37a97e2c3112d02cafaaa4a01cb19be5656cfc9f
Fourth step of ADR-0009 wiring. Closes the OIDC round-trip on the
BFF side (modulo session persistence — that's the next PR per
ADR-0010). Entra redirects the user back to `/api/auth/callback`
with a `code` + `state`; the BFF verifies the state, exchanges the
code for tokens via MSAL Node's `acquireTokenByCode`, runs the
ADR-0011 `amr` sanity-check, logs the resolved identity to Pino,
clears the single-use pre-auth cookie, and 302s the user back to
the SPA.
What lands:
- `apps/portal-bff/src/auth/auth.errors.ts` — discriminated-union
`AuthCodeFlowError` (state-mismatch / flow-expired / amr-missing
/ token-exchange-failed) + `AuthCodeFlowException` wrapper. The
`kind` field doubles as the `?auth_error=<code>` query param on
the SPA-bound redirect, so the front-end can render an exact
message without duplicating the string set.
- `AuthService.completeAuthCodeFlow(code, state, preAuth, now?)` —
verifies state binding, refuses cookies older than the 5-minute
flow TTL, calls MSAL, validates `amr` is non-empty (ADR-0011 BFF
sanity-check; Conditional Access on the org side is the real
enforcement), extracts the four `oid` / `tid` /
`preferred_username` / `name` claims plus the `amr` array into
an `AuthenticatedUser` shape.
- `auth.cookie.ts` gains `clearPreAuthCookieOptions()` mirroring
the set-options minus `maxAge` so the browser actually drops the
cookie (cookies match by name + path + secure; getting any of
those wrong leaves the old cookie in place).
- `AuthController.callback()` — `@Get('callback')`:
1. Always `res.clearCookie(...)` first. The cookie is single-use
by design; a parallel /login overlap should not survive.
2. Bail on Entra-side errors (`?error=`), missing query params,
missing or malformed pre-auth cookie. Each branch logs a
structured Pino warning and redirects with the right
`auth_error` code.
3. Call the service. On `AuthCodeFlowException`, log + redirect
with the typed `kind`.
4. On success, log a `auth.signed_in` event with `oid`, `tid`,
`username`, `amr` (PII-sensitive bits only; no tokens), then
302 to `entra.postLogoutRedirectUri` (reused as the SPA root
URL — a dedicated `SPA_BASE_URL` env var lands if the two
URLs ever need to diverge).
- The controller now takes `Logger` + `ENTRA_CONFIG` via DI
alongside `AuthService`.
Verification:
- `nx run-many -t lint test build --projects=portal-bff` — green.
- 52/52 specs (was 39; +13 across the new service-completeFlow spec
branches and the controller-callback spec branches). Service
spec covers happy path + 6 failure modes (state mismatch, flow
expired, amr missing, MSAL throws, MSAL returns null, oid claim
missing). Controller spec covers happy redirect, Entra error
branch, missing cookie, AuthCodeFlowException branch, missing
query, malformed cookie.
What this PR explicitly does NOT do:
- Persist a session. The user is "authenticated" in the BFF's
point of view (we have their identity) but the next request lands
anonymous. Closes in the Redis sessions PR (ADR-0010).
- Audit log entry. The audit module is in place; wiring the
`auth.signed_in` event into it lands alongside sessions so the
audit row carries a `session_id`.
- Logout / `/me`. Land after sessions.
Documentation index
This is the entry point to all project documentation. It is maintained automatically: any addition, rename, or removal of a .md file under docs/ must be reflected here in the same change.
Conventions
- Documentation is written in English.
- One topic per file. Group related files into a folder when there are three or more.
- Cross-reference with relative links so they keep working in GitHub, IDEs, and exported sites.
- For architectural decisions, do not add them here — they belong in decisions/ as MADR 4.0.0 ADRs.
Sections
Daily development
- development.md — repo layout, prerequisites, initial setup, daily commands, observability dev-loop (Jaeger UI, log ↔ trace correlation), dependency updates (Renovate), conventional commit cycle. Day-to-day reference for working on the project.
Architecture
- architecture.md — cross-cutting Mermaid diagrams: C4 system context, C4 containers, Nx module boundaries, CI/CD pipeline. Single-decision diagrams (auth sequence, ERD, etc.) live inline in their ADR.
Onboarding & environment
Setup guides for new contributors:
- setup/01-wsl-terminal-setup.md — modern WSL terminal (Zsh + Powerlevel10k + CLI tools)
- setup/02-dev-web-stack.md — Node via nvm, pnpm via corepack, Docker
- setup/03-angular-nx-monorepo.md — Angular + Nx monorepo bootstrap
Operations & runbooks
Empty — to be populated when we deploy.
Security, performance, accessibility
Empty — placeholders to be filled with rationale docs alongside their corresponding ADRs.
Description
Languages
TypeScript
85.3%
JavaScript
5.4%
SCSS
4.3%
HTML
3.9%
Shell
0.8%
Other
0.3%