Skip to content

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

  1. src/app/login/page.tsxsignIn("oidc", { callbackUrl })
  2. Auth.js handles the callback at src/app/api/auth/[...nextauth]/route.ts
  3. JWT callback auth.config.ts stores OIDC tokens, then calls fetchBackendIdentity()
  4. Session callback projects { id, username, email, role } onto session.user.
  5. Encrypted session cookie set,
  6. 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.