SSO Callback Returns 400 / 401¶
The "Sign in with X" button on the login page redirects you to the IdP, you authenticate, you get bounced back to MFCloud — and the console shows an error instead of the dashboard.
400: Stale or missing OIDC flow cookie — start sign-in again.¶
The signed cookie that holds (state, nonce, pkce_verifier) is missing or expired.
Causes:
- The flow took longer than 10 minutes (cookie TTL). Click the SSO button again — fresh state.
- The browser stripped the cookie because you came back to a different hostname than you started from (e.g. started at https://mfconsole.com, came back to https://192.168.10.24). Always use the same hostname for start and callback. Caddy's site blocks are case-sensitive on hostname.
- A third-party cookie blocker (or strict tracking-protection mode) is dropping the cookie. The cookie is SameSite=Lax, which IdP redirects DO carry — but some "block all cross-site cookies" toggles still nuke it.
400: state mismatch — possible CSRF¶
The state parameter the IdP echoed back doesn't match the one in the cookie. Almost always a stale browser tab — you started the flow in tab A, then clicked SSO again in tab B which overwrote the cookie, then came back to tab A's callback. Close the extra tabs and try once cleanly.
400: IdP returned error 'invalid_grant' / similar¶
The IdP itself is rejecting our token exchange. The error_description from the IdP is included verbatim — read it.
Common cases:
- Wrong redirect URI registered. The redirect URI MUST match byte-for-byte between /start, /callback, and what's listed in your IdP app. If the master is behind Caddy, make sure Uvicorn is started with --proxy-headers so X-Forwarded-Proto: https is honored.
- Stale auth code. You hit Back/Forward in the browser and the IdP rejected the re-use. Click the SSO button again.
401: ID token rejected: nonce mismatch¶
You started one flow, abandoned it, started another — and the new IdP response is being checked against the new cookie's nonce. Same fix as state mismatch: close extra tabs, try once.
401: ID token rejected: ... 'aud' is required¶
The IdP isn't including your client_id in the token's aud claim. Check the OIDC app at the IdP — Entra has a "Token configuration → optional claims" section; Okta has "Sign On → OpenID Connect ID Token". Add the audience and retry.
404: OIDC is not enabled¶
Self-explanatory — the SSO settings toggle is off. Settings → Single Sign-On → Enable and Save.
Diagnostic — read the master logs¶
The router prints request shape, IdP response status, and any token validation error in plaintext. Far faster than reverse-engineering from the browser.
Cache invalidation¶
If you've changed the Issuer URL in Settings → SSO, the discovery doc + JWKS were cached for up to 60 min and might still serve the old endpoints. The save flow now calls oidc_auth.clear_caches() automatically, but if you're using curl to test the settings endpoint directly you'll skip that — restart the app container to force-flush.