Dashboard: Authentication
The console wraps its entire UI in a LoginGate component (pages/auth/LoginGate.tsx) that handles login before anything else renders. This page documents the actual browser-side login UX; see guides/security/authentication.md for the server-side [auth] config (mode = "local" | "oidc" | "disabled") that determines which of the paths below is live.
Flow: OIDC (Authorization Code + PKCE)
When auth.mode = "oidc", the console implements a standard browser-based OIDC login (api/auth.ts) — Dex, Microsoft Entra, Google Workspace, or any standards-compliant IdP:
- On load, the console checks for a stored token. If missing or expired, it shows a "Sign In" card and redirects to the IdP on click.
- The user authenticates at the IdP.
- The IdP redirects back to the console with
?code=.... - The console exchanges the code for tokens using PKCE (no client secret ever touches the browser) — the code verifier and CSRF
stateare round-tripped throughsessionStorage. - The resulting
id_tokenis stored insessionStorage(notlocalStorage— it does not survive a browser restart) and attached as aBearertoken on every subsequent API call.
The server never sees login credentials directly — it only validates the JWT the IdP issued.
LoginGate states
The gate is a small state machine (loading → disabled | unauthenticated | authenticated):
| State | Meaning | What renders |
|---|---|---|
loading | Checking for a ?code= callback, then fetching OIDC config | A centered "Loading..." card |
disabled | Server reports auth.mode is not OIDC-backed | The app renders directly — dev-mode/no-auth bypass |
unauthenticated | OIDC enabled, no valid token | A "Sign in with your identity provider" card with a Sign In button |
authenticated | Valid, unexpired token present (or a callback just succeeded) | The app renders normally |
A stored token that has expired is cleared automatically before falling back to unauthenticated — the user sees a fresh login prompt rather than an error about the session being expired for its own sake.
Dev-mode bypass
If the server's OIDC config reports enabled: false (i.e. auth.mode = "local" or "disabled"), the gate renders the app directly with no login step at all. This is by design for local/auth.mode = "local" deployments — those use the same API-key infrastructure as the rest of the platform (POST /auth/keys) rather than a browser login flow; the console doesn't currently have its own API-key entry UI, so a local-mode deployment's console access is effectively unauthenticated unless fronted by something else.
What's not here
There is no role-aware UI gating in the console today — see security-overview.md for what that means in practice (the identity/role is displayed, not enforced client-side).
Related:
- guides/security/authentication.md — server-side
[auth]config and modes - security-overview.md — how (little) RBAC currently surfaces in the console UI
- reference/api/auth.md — the API-key and OIDC session endpoints the console calls