Runbooks / Authentication

Authentication

IdentityMesh’s Admin API supports two caller-authentication schemes:

  1. Windows Negotiate (Kerberos / NTLM over SPNEGO). The default. Works with domain-joined hosts and AD-issued credentials. No configuration required.
  2. JWT Bearer (OIDC / Entra ID). Opt-in. Enables cloud IdP scenarios — users signed in via Entra, tokens minted by the Microsoft identity platform, presented as Authorization: Bearer <jwt>.

Both can be active simultaneously. A "SmartAuth" policy scheme in the pipeline inspects every request: if it carries a Bearer token, the JwtBearer handler validates it; otherwise Negotiate runs as before. Browsers continue to do SPNEGO even when the JWT path is enabled.

When to enable JWT

If your deployment is entirely on an AD-joined LAN, the default Negotiate-only config is fine — skip this document.

Configuration

Set three keys under Authentication:Jwt in appsettings.json (or the equivalent environment variables — ASP.NET Core’s default binding uses Authentication__Jwt__… on Linux/containers):

"Authentication": {
  "Jwt": {
    "Enabled": true,
    "Authority": "https://login.microsoftonline.com/<tenant-id>/v2.0",
    "Audience": "<api-app-id-or-uri>"
  }
}

The service fails to start if Enabled=true and either Authority or Audience is missing — prefer a loud startup error over a silent fallback.

One app registration, two purposes: identifies the API (audience), and defines the app roles that map to IdentityMesh permissions.

1. Create the app registration

Entra portal → App registrations → New registration.

After creation, grab:

App registration → Expose an API:

3. Define app roles

App registration → App roles → Create three:

Display nameValueAllowed member types
IdentityMesh AdminIdentityMesh.AdminUsers/Groups (and optionally Applications for M2M)
IdentityMesh OperatorIdentityMesh.OperatorUsers/Groups
IdentityMesh ViewerIdentityMesh.ViewerUsers/Groups

Entra emits the assigned app-role values into the token’s roles claim. Our JwtBearer configuration sets RoleClaimType = "roles", so ClaimsPrincipal.IsInRole("IdentityMesh.Admin") lights up for callers who have that assignment.

4. Wire the role names into IdentityMesh config

Map the role values to IdentityMesh’s built-in role names in IdentityMesh:Roles:

"IdentityMesh": {
  "Roles": {
    "Admin": "IdentityMesh.Admin",
    "Operator": "IdentityMesh.Operator",
    "Viewer": "IdentityMesh.Viewer"
  }
}

RoleResolverService checks these values against ClaimsPrincipal.IsInRole(...) — no code change is needed, same resolver that handled Windows groups handles Entra app roles.

If you run a mixed deployment (some users on Windows groups, some on Entra app roles), rename the Entra app roles to match your AD group names — e.g. set both to Corp\IdentityMesh-Admins. IsInRole does a string-equals over every role claim the principal carries, so one config value can satisfy both populations.

5. Assign users / groups

Entra portal → Enterprise applications → IdentityMesh Admin API → Users and groupsAdd user/group → pick the principal and the role. Without an assignment the user authenticates successfully but falls through to the Viewer default (which is what the existing resolver does for unmapped Windows users).

Verification

End-to-end with a real token

# Acquire a token for yourself via Azure CLI (requires az login first)
$token = az account get-access-token `
    --resource <audience-value> `
    --query accessToken -o tsv

# Hit a gated endpoint
curl -H "Authorization: Bearer $token" https://<api-host>/api/license

A successful 200 + JSON body means: token validated, role resolved, permission check passed.

Smoke-check the scheme selector

With JWT enabled, the same host should still accept Negotiate — a request from a domain-joined browser (no Authorization header) hits the Negotiate branch, a request with a Bearer token hits JwtBearer. The 401 WWW-Authenticate header on an anonymous call surfaces whichever scheme the challenge scheme picked; confirms multi-scheme wiring didn’t regress SPNEGO.

Operational notes

Notes