Authentication¶
Commit scope: auth · Repos: api, frontend
Overview¶
Shape¶
- Auth.js
- OIDC + JWT-only sessions
- BFF token-injection
- PKCE, state, nonce
The frontend owns the session, the backend is stateless. The backend validates the OIDC bearer token on every request and JIT-provisions the user from token claims. No DB sessions on either side.
Authentic in prod, nanoidp in dev.
Sign-in flow¶
src/app/login/page.tsx→signIn("oidc", { callbackUrl })- Auth.js handles the callback at
src/app/api/auth/[...nextauth]/route.ts - JWT callback
auth.config.tsstores OIDC tokens, then callsfetchBackendIdentity() - Session callback projects { id, username, email, role } onto session.user.
- Encrypted session cookie set,
- Redirect to /admin.
Token refresh¶
The frontend middleware (src/proxy.ts) is the single owner of access-token
refresh. On every matched request it decodes the session JWT; when the access
token is within 60 s of expiry it runs the OIDC refresh_token grant
(single-flight per refresh token) and propagates the re-encoded session cookie
in both directions:
- onto the forwarded request headers, so the RSC render in that same request forwards a live bearer (server reads decode the cookie raw and never refresh), and
- onto the response as
Set-Cookie, so the browser persists the rotation.
One refresher means a rotating IdP never sees the same refresh token spent
twice. If the refresh fails on /admin/* (refresh token expired or revoked)
the middleware redirects to /login, which re-auths through the IdP session —
instant while that session is live. Public routes fall through anonymously.
The Auth.js jwt callback still stores tokens at sign-in but no longer
refreshes per-request (nothing polls /api/auth/session).
Identity endpoints¶
| Endpoint | Returns |
|---|---|
GET /me |
The current user (id, email, name, role, active flag). |
GET /me/permissions/{id} |
The current user's capabilities (self only). |
GET / PATCH /me/settings |
The current user's settings. |
The role-to-permission rules these endpoints reflect are documented in User roles.