> ## Documentation Index
> Fetch the complete documentation index at: https://docs.findmydata.io/llms.txt
> Use this file to discover all available pages before exploring further.

# ADR 0004 authentication dev mode and entra

# ADR-0004: Authentication — explicit dev identity mode, Entra OIDC seam

**Status:** Accepted · 2026-07-14

## Decision

* Two authentication modes selected by `FMD_AUTH_MODE`, sharing one session
  layer and one downstream authorization stack:
  * **Dev identity mode** (`FMD_AUTH_MODE=dev`) — the **implemented and exercised**
    path in this build. Sign-in is a persona picker over seeded fixture principals
    (`POST /api/auth/dev/login`, `GET /api/auth/dev/personas`). Every API response
    carries `x-fmd-auth-mode: dev`; the UI shows a persistent, non-dismissable
    "Development identities — not for production" banner. All feature tests run
    through this path.
  * **Entra OIDC** (`FMD_AUTH_MODE=entra`) — the **intended production path,
    present only as a seam in this build**. The seam is a single endpoint,
    `GET /api/auth/entra/login`, which returns an explicit **501 `not_configured`**
    (never a silent fallback to dev), and 404s when `authMode !== "entra"`. There
    is **no** OIDC code-flow implementation, token/JWT validation, or issuer/
    audience-verification code yet — that is deliberately deferred work
    (`packages/server/src/identity/auth-routes.ts`). The standards-based flow
    (authorization code, issuer/audience/tenant validation, nonce/state,
    short-lived sessions) is the design contract to implement against this seam.
* **Production lockout (implemented + tested):** if `FMD_ENV=production`, the
  server refuses to boot with `FMD_AUTH_MODE=dev` and with the default
  `FMD_SESSION_SECRET` (hard `ConfigError` at startup — `kernel/config.ts`), and
  dev-mode session cookies are rejected at request time even if present
  (`kernel/http.ts`).
* The data model reserves the external-identity mapping for the future OIDC path:
  a `principals` row is keyed to an identity provider by an immutable
  `(tenant_id, issuer, subject)` tuple — never email/display name. This exists
  today only as the `uq_principals_issuer_subject` unique index
  (`migrations/0001_identity_governance.sql`); dev-mode principals use
  `external_id` and are not populated via `issuer`/`subject`.

## Consequences

* All authorization logic downstream is identical regardless of how the session
  was established; the authz stack is fully exercised through dev identities
  without weakening the production path — but the production sign-in path itself
  is **not yet built** and must be implemented on the 501 seam before any
  production deployment (tracked in `docs/roadmap.md` and `STATUS.md`).
* Session mechanics (implemented): a signed, HTTP-only, `SameSite=Lax` session
  cookie (`fmd_session`, `secure` in production), a per-session CSRF token
  required via the `x-fmd-csrf` header on all mutating routes, and server-side
  session revocation (`identity/sessions.ts`, `kernel/http.ts`).
