Auth API
Generic OIDC (discovery + PKCE) with stateless HS256 session JWTs. See
Authentication for the design.
All JSON responses/errors and redirects explicitly send Cache-Control: no-store.
Start login
GET /api/auth/login
- With
AUTH_DISABLED=true, redirects to/; requests use the mock local user without minting a session JWT or requiring a signing key. - Without OIDC configured, answers
501(operator must setOIDC_ISSUER/OIDC_CLIENT_ID, orAUTH_DISABLED=truefor local dev). - Otherwise requires a valid
SESSION_SECRETand redirects to the IdP authorization endpoint. Invalid signing-key configuration or an unreachable IdP answers502with fixed error text and any available allowlisted cause code/fixed hint. Server logs classify configuration/network failures without raw messages, hostnames, transport headers, token/claim values, or issuer URLs.
OIDC callback
GET /api/auth/callback?code=…&state=…
Exchanges the code, maps claims (email identity, groups authz input,
name display), sets the krypton_session cookie (HttpOnly,
Secure in production, SameSite=Lax, 8h default), and redirects to /.
All state, nonce, and PKCE cookies must be present and nonempty before any token exchange. Every callback attempt consumes them, including failures; start a new login to retry. State and nonce must match, the IdP checks the PKCE verifier, and an ID token with a subject is required. UserInfo supplements claims only when its subject matches that ID token; mismatch rejects login. Missing/unavailable optional UserInfo falls back to ID-token claims.
On failure (including IdP verdicts like invalid_grant from a redirect-URI
mismatch), redirects to /login?auth=error with an optional idp_error.
Only known OAuth/OIDC codes (such as invalid_grant, invalid_scope, and
access_denied) are retained; unknown, malformed, or oversized codes become
provider_error. The code classifier accepts at most 64 characters and never
copies arbitrary provider codes into URLs or logs.
Diagnostic upgrade: raw error_description, code/state values, and upstream
messages/headers are omitted from failure redirects and logs. Descriptions are
classified as descriptionCode: redirect_uri, provided, or omitted (over 2 KiB).
At most eight error causes and 2 KiB per text diagnostic are examined. Logs retain
allowlisted system/library codes, fixed operator hints, and a boolean
redirectBaseMatches comparing request/configured redirect origins and paths.
This can be false behind a proxy; the exchange still normalizes to the configured
public redirect. Login 502 responses no longer include a raw issuer URL.
Current user
GET /api/auth/me
{ "user": { "sub": "…", "email": "alice@company.com", "name": "Alice", "groups": ["dev-team"] } }
Answers 401 { "user": null } when unauthenticated — the one endpoint that
does not use the { "error": … } shape. Invalid signing-key configuration,
expired/forged sessions, unsupported JWT algorithms, and malformed required
claims are unauthenticated. See session validation.
Logout
GET /api/auth/logout
POST /api/auth/logout
Both clear the session cookie. GET redirects to /login; POST answers
{ "ok": true } for fetch-based sign-out buttons.