Pin the BFF authentication mechanics: OAuth 2.0 Authorization Code Flow with PKCE, executed server-side via @azure/msal-node's ConfidentialClientApplication. Tokens are held in the BFF session and never reach the browser; the SPA only ever sees the opaque __Host-portal_session cookie. Token validation enforces the tenant allowlist from ADR-0008 (iss check) and maps the audience claim to our Audience enum at the validation step. Refresh-token rotation is enabled via MSAL acquireTokenSilent. Cookies use the __Host- prefix (forces Secure/Path=/, no Domain) with HttpOnly/SameSite=Lax. CSRF uses the double-submit pattern with a matching X-CSRF-Token header on every state-changing request, enforced by a NestJS interceptor and injected client-side by an Angular HTTP interceptor. Logout is RP-initiated against Entra's end_session_endpoint. Routes are pinned: GET /auth/login, GET /auth/callback, POST /auth/logout, GET /auth/me. AuthGuard is registered globally - public routes must be explicitly opted in. Local dev runs over HTTPS via mkcert to keep cookie behaviour identical to prod. All Entra-specific values come from environment variables; the BFF refuses to start without them. decisions/README.md index updated. CLAUDE.md gains an explicit 'Authentication flow' line pointing to ADR-0009.
Architectural Decision Records
This project records architecturally-significant decisions as ADRs in the MADR 4.0.0 format. References: adr.github.io.
Why ADRs
ADRs capture the why behind a decision — context, drivers, options considered, trade-offs accepted — at the moment the decision is made. They make architecture reviewable, onboarding faster, and prevent the same debate from being re-litigated later.
Conventions
- Format: MADR 4.0.0. Start from template.md.
- Filename:
NNNN-kebab-case-title.md, e.g.0007-adopt-tailwind-for-design-tokens.md. - Numbering: globally sequential 4-digit prefix. Numbers never reset, never get reused — even when an ADR is superseded or deprecated.
- Layout: flat folder. ADRs are not nested into category subfolders; topical organization happens via tags.
- Tags: every ADR carries a
tags:array in the MADR frontmatter, drawn from the tag vocabulary below. An ADR may carry several tags. Propose new tags (or renames) in the same PR that needs them; never invent ad-hoc tags inline. - Status lifecycle:
proposed→accepted→ optionallydeprecatedorsuperseded by [ADR-NNNN](NNNN-other.md). Update the YAML frontmatter; never delete an ADR. - Index maintenance: every ADR addition or status change must update the Index below in the same commit.
When to write an ADR
Write one whenever a development decision is non-trivial: tool or library choice, framework pattern, security control, perf budget, a11y target, naming convention, deprecation, breaking change, or any choice that future contributors would benefit from understanding the why of.
Tag vocabulary
The vocabulary below is the source of truth. It is intentionally coarse — propose extensions only when an existing tag genuinely doesn't fit, and avoid overly narrow tags.
| Tag | Scope |
|---|---|
frontend |
UI, Angular, components, design system, client-side state |
backend |
API, BFF, server-side services |
security |
AuthN, AuthZ, sessions, CSP, dependency scanning, secret management |
performance |
Perf budgets, caching, bundle size, Lighthouse |
accessibility |
WCAG, a11y testing, keyboard, ARIA, contrast |
infrastructure |
CI/CD, hosting, deployment, runtime |
observability |
Logs, metrics, traces, correlation IDs, monitoring |
data |
Persistence, schemas, migrations, data flow |
process |
Team conventions, workflows, repo policy |
Status: starter vocabulary, to be refined as ADRs accumulate. Update this table whenever a tag is added, renamed, or retired.
Index
ADRs are listed in numerical order. To slice by topic, filter on the Tags column.
| # | Title | Status | Tags | Date |
|---|---|---|---|---|
| 0001 | Use ADRs to record architectural decisions | accepted | process |
2026-04-29 |
| 0002 | Adopt Nx monorepo with the apps preset |
accepted | infrastructure, frontend, backend |
2026-04-29 |
| 0003 | Workspace and app naming convention | accepted | process |
2026-04-29 |
| 0004 | Frontend stack — Angular (latest LTS), standalone, zoneless, Signals, CSR-only, Vitest | accepted | frontend |
2026-04-29 |
| 0005 | Backend stack — NestJS over Express, Fastify, Hono | accepted | backend |
2026-04-29 |
| 0006 | Persistence — PostgreSQL with Prisma | accepted | data, backend |
2026-04-29 |
| 0007 | Pre-commit hooks and Conventional Commits | accepted | process |
2026-04-29 |
| 0008 | Identity model — multi-tenant Entra ID for workforce, dual-audience design for future External ID | accepted | security, data |
2026-04-29 |
| 0009 | Authentication flow — OIDC Authorization Code + PKCE via MSAL Node, BFF session pattern | accepted | security, backend |
2026-04-29 |