Identity provider#
Users sign in at the gateway with standard OpenID Connect. Entra ID, Keycloak and One Identity
are each just an authority; the backends never talk to the provider. They trust a token the gateway
signs (docs/authorization.md). This page covers what the gateway reads, the three ways it proves
its own identity to the provider, the fallback to the deployment's settings, and how to recover when
sign-in is broken.
flowchart LR
Browser -- "/.login" --> BFF["Gateway"]
BFF -- "authorize (code + PKCE)" --> IdP["Identity provider<br/>Entra ID · Keycloak · One Identity"]
IdP -- "form_post /signin-oidc" --> BFF
BFF -- "token request:<br/>secret · certificate · managed identity" --> IdP
BFF -- "user token (ES256)" --> Backends
AC["App Configuration<br/>Authentication:Oidc (platform/bff)"] -. "at start" .-> BFF
Dep["Deployment settings<br/>AzureAd"] -. "fallback" .-> BFF
What the gateway reads#
The settings live in Authentication:Oidc at label platform/bff. Without an authority there, the
gateway uses the deployment's AzureAd section, read as Microsoft.Identity.Web read it (below).
| Key | Default | |
|---|---|---|
Authority |
The issuer, for example https://login.microsoftonline.com/{tenant}/v2.0, https://keycloak.example/realms/cc. Must be https (http only in Development, on localhost or a container name). A multi-tenant Entra ID authority (common, organizations) is refused: it would sign in any tenant. |
|
ClientId |
The gateway's client at the provider. | |
ClientAuthentication |
secret |
secret, certificate or managed-identity (below). |
ClientSecret |
For secret: a Key Vault reference, never a plain value. |
|
ClientCertificate / ClientCertificatePath |
For certificate: the certificate with its private key, as base64 PKCS#12 or PEM (typically a Key Vault reference to a Key Vault certificate), or a file. ClientCertificatePassword when the PKCS#12 has one. |
|
ManagedIdentityClientId |
the host's identity | For managed-identity: a user-assigned identity, when the host has several. |
Scopes |
openid profile |
Space-separated; must include openid. |
CallbackPath |
/signin-oidc |
Where the provider sends the user back. |
SignedOutCallbackPath |
/signout-callback-oidc |
|
GetClaimsFromUserInfoEndpoint |
false |
Ask the userinfo endpoint too, when the provider puts roles there rather than in the ID token. |
Authentication is restart-bound: a change applies when the gateway restarts. The Hosts panel
(/config/hosts) shows the pending restart and restarts it, one instance at a time.
Always the same:
- the authorization code flow with PKCE, with the answer as a form POST;
- the session cookie (HttpOnly, Secure, SameSite Strict);
- no OAuth token is kept after sign-in;
- the provider's own claim names are used, with no inbound mapping. The role map (/config/access)
turns the provider's roles into internal roles.
Proving the gateway's identity#
ClientAuthentication |
How | Works with | Set up at the provider |
|---|---|---|---|
secret |
client_secret in the token request |
every provider | a client secret. The simplest option, but it expires and must be rotated. |
certificate |
private_key_jwt: a JWT the gateway signs with its certificate (iss and sub the client id, aud the token endpoint, five minutes; the x5t header names the certificate) |
Entra ID, Keycloak ("Signed JWT"), One Identity | upload the certificate's public part (Entra ID: Certificates & secrets; Keycloak: the client's Keys tab, or a JWKS URL). Works on localhost too. |
managed-identity |
the host's managed identity gets a token for api://AzureADTokenExchange, and that token is the client assertion |
Entra ID only | a federated credential on the app registration for that managed identity. No secret or certificate exists anywhere: the best choice inside Azure. |
A certificate is loaded when the gateway starts. The managed identity is asked at each sign-in.
Changing it on /config/identity-provider#
The page shows what the gateway signs in with now (GET /bff/identity-provider) next to what
is stored (Authentication:Oidc at label platform/bff). With idp.write it edits:
- the authority and client id;
- the client authentication. A secret or certificate is named in the host's Key Vault, never
typed in; a managed identity can take a user-assigned identity's client id;
- the scopes, the callback paths and the userinfo option.
journey
title Moving the gateway to another provider
section At the provider
Register the gateway (the page lists both URIs): 3: Admin
Add a secret, a certificate or a federated credential: 3: Admin
section On the page
Enter the authority, client id and credential: 4: Admin
Check the provider (discovery document): 5: Admin
Save: 5: Admin
section Apply
Restart the gateway on the Hosts page: 4: Admin
Sign in once to confirm: 5: Admin
- Check first. "Check the provider" fetches
{authority}/.well-known/openid-configurationfrom WebApi. Saving stays off until a check of the values being saved has passed.- Errors (the gateway could not sign in):
- the issuer is not the authority;
- an endpoint is missing or not https;
- the code flow or
form_postis not offered; - a certificate or managed identity without
private_key_jwt; - a managed identity on a provider other than Entra ID.
- Warnings: no PKCE S256 listed, or no
end_session_endpoint(sign-out then ends only the gateway's session). - The fetch is fenced: it needs
idp.write, https (http only in Development, on localhost or a container name), no redirects, 10 seconds and 512 KB at most.
- Errors (the gateway could not sign in):
- Saving checks again, server-side:
- the settings as the gateway reads them;
- that the named secret or certificate exists, is enabled and has not expired in WebApi's vault;
- the discovery document.
Only then does it write one JSON value and the one Key Vault reference the mode needs; any other
stored key under Authentication:Oidc is removed. It bumps the platform sentinel and writes
audit event 3195. The version read makes it conditional (409).
- The URIs to register at the provider are this page's origin plus the callback paths.
- Reset removes what is stored (audit 3196): after its restart the gateway uses the deployment's
AzureAd settings again.
- Nothing applies until the gateway restarts. The page says so while a gateway instance still
runs the previous settings, and links to the Hosts page.
A name in a different vault from the gateway's, or a reference the gateway cannot resolve, leaves
the gateway without its credential. It then falls back to AzureAd (EventId 19); the Credentials
page lists the reference as unresolved on the gateway.
The fallback, and a mistake in the stored settings#
The gateway takes Authentication:Oidc only when it is usable: it validates, and its certificate
loads. Otherwise it signs in with the deployment's AzureAd settings and logs why, critically
(EventId 19). A mistake saved in the store therefore falls back to the deployment's provider
instead of locking every user out. At start it logs which provider users sign in with (EventId 18).
AzureAd is read as Microsoft.Identity.Web read it:
- the authority comes from Instance and TenantId (or from Authority);
- the client credential is the first usable one in ClientCredentials, then in
ClientCertificates, then ClientSecret;
- supported SourceTypes:
- ClientSecret;
- SignedAssertionFromManagedIdentity, with ManagedIdentityClientId;
- Base64Encoded, Path (CertificateDiskPath, CertificatePassword);
- KeyVault (KeyVaultUrl, KeyVaultCertificateName; read with the host's identity);
- StoreWithThumbprint.
A deployment that worked with Microsoft.Identity.Web works unchanged.
GET /bff/identity-provider (signed in) reports what the gateway runs: the authority, the client
id, the client authentication, where the settings came from, the certificate's thumbprint and
expiry, and the problems found at start. Never a secret or a key.
Recovering without the UI#
When nobody can sign in:
- Remove the stored settings, so the gateway falls back to the deployment's:
- Move the platform sentinel on, so every host reloads:
- Restart the gateway (
docker restarton the VM, or the Hosts panel from a session that still works).
Restarting a container keeps its data-protection keys, so existing sessions survive. Recreating it (a new deployment) signs everyone out, because the gateway does not persist those keys.
What changed with the move from Microsoft.Identity.Web#
- The gateway no longer gets on-behalf-of tokens for downstream APIs: the route metadata
RequiredScopesandAuthenticatedForwarderTransformerare gone. No route used them; the backends learn the user from the gateway's own user token. - The principal carries the provider's claim names (
oid,sub,preferred_username,roles,realm_access).SubjectNormalizerread those already, so an Entra ID user keeps the same subject. - Microsoft.Identity.Web error pages (for example on a consent error) are replaced by the standard handler's; a failed sign-in shows the provider's error.