Login / OIDC issues¶
Symptoms: clicking "Log in" loops back to the IdP without landing on the dashboard, or returns an error from Ampora, or the IdP rejects.
1. Issuer mismatch¶
The single most common cause. The URL in Authentication:Oidc:Authority must equal the iss claim in returned ID tokens exactly — including trailing slash, scheme, and port.
Check your IdP's discovery document:
The string in .issuer is what your Authority setting must match.
2. Redirect URI not whitelisted¶
The IdP's reply URL must be one the IdP knows about:
- For Keycloak / Okta: Valid Redirect URIs must include
https://AMPORA_HOST/signin-oidc. - For Azure Entra ID: under Authentication → Redirect URIs.
- For path-mounted Ampora (
https://AMPORA_HOST/ampora/...), includehttps://AMPORA_HOST/ampora/signin-oidc.
3. Client secret mismatch¶
The IdP returned invalid_client or unauthorized_client:
- The secret in
Authentication:Oidc:ClientSecretis wrong, or - The secret was rotated in the IdP and not in Ampora.
Re-check, redeploy. The .NET OIDC handler defaults to client_secret_post. If your IdP requires client_secret_basic, set Authentication:Oidc:ClientAuthentication=ClientSecretBasic.
4. Role / tenant claim missing¶
The user authenticates fine but Ampora returns AccessDenied after the callback:
- The role claim is missing from the ID token. Verify with jwt.io by pasting the token (decode only, never the signature).
- The
Authentication:Oidc:RoleClaimsetting points at a claim name the IdP does not actually emit. - For multi-tenant: the tenant discriminator claim is missing or maps to a tenant that does not exist.
Adjust the IdP's claim mapping or the Ampora setting.
5. Clock skew¶
If Ampora rejects ID tokens with "token not yet valid" or "token expired", check the clocks. .NET's OIDC handler tolerates 5 minutes of skew by default, but on a freshly provisioned VM or container the clock can be wildly off.
Compare against date -u on the IdP. Both should be NTP-synced.
6. CORS / front-channel logout¶
If logout succeeds but the user is not bounced back:
Authentication:Oidc:CallbackPathand the IdP's Front-channel Logout URL must agree.- For SameSite-strict cookies, the logout endpoint must be on the same registrable domain.
7. host.docker.internal pitfall (local stack)¶
The local Compose stack uses host.docker.internal so the browser and the Ampora container resolve Keycloak through the same hostname. On Linux without Docker Desktop, add to /etc/hosts:
Without this, the Ampora container resolves to the Linux host, the browser does not, and the issuer claim mismatches between the two.
8. PKCE rejected¶
Some older IdPs reject PKCE if the client is configured as "confidential". The .NET OIDC handler enables PKCE by default. Either:
- update the IdP client config to allow PKCE (the modern default), or
- set
Authentication:Oidc:UsePkce=false(not recommended; weakens the code interception protection).
9. Logs¶
Ampora logs OIDC failures with the category Microsoft.AspNetCore.Authentication.OpenIdConnect. Filter your log shipper:
The exception message names the failed step (MessageReceived, AuthorizationCodeReceived, TokenValidated).
10. Bypass for emergency¶
The first user to log in becomes Admin if no users exist. If you have broken role mapping for every user and locked yourself out:
- Wipe the
userstable (in dev) — the next login bootstraps the new user as Admin. - In production, use the support-tool admin script (under
tools/support/) which can flip a known user to Admin via database access. Never expose this in a normal RBAC flow.