docs: add ADR-0011 for MFA enforcement (Entra Conditional Access + step-up hooks)
Pin the MFA policy: enforcement lives in Entra ID Conditional Access at the tenant level (org IT responsibility) - the application code does not implement MFA mechanics. The BFF performs a defense-in-depth sanity-check on the id_token amr claim at session creation; sessions without evidence of multi-factor authentication are rejected. The accepted amr values are maintained in a small in-source list and reviewed on cadence. Step-up MFA is designed-in for v1 but dormant: a @RequireMfa() decorator and a RequireMfaGuard ship in the codebase, the session payload carries mfaVerifiedAt, and the SPA HTTP interceptor handles the 401 + claims challenge round trip. No v1 route is annotated, since v1 has no admin UI or other operations sensitive enough to require fresh MFA. The hooks are kept alive by automated tests so they don't drift. Authentication Context Classes (ACR-based step-up) are not used in v1; they remain a future option if specific operations later demand them. Service-account / app-only flows are out of scope. decisions/README.md index updated. CLAUDE.md gains an explicit 'MFA' line pointing to ADR-0011.
This commit is contained in:
@@ -37,6 +37,7 @@ The structural choices are recorded as ADRs and summarized below. Any change to
|
||||
- **Backend (`portal-bff`):** NestJS at the latest stable major, mounted on the Express adapter (Fastify adapter swappable later) — see [ADR-0005](decisions/0005-backend-stack-nestjs.md).
|
||||
- **Persistence:** PostgreSQL (latest stable major) via Prisma — see [ADR-0006](decisions/0006-persistence-postgresql-prisma.md).
|
||||
- **Sessions:** opaque session id in `__Host-portal_session`, payload in self-hosted Redis (Sentinel HA in prod, single node in dev), tokens encrypted at rest with AES-256-GCM, idle 30 min sliding + absolute 12 h — see [ADR-0010](decisions/0010-session-management-redis.md).
|
||||
- **MFA:** enforced by Entra ID Conditional Access (org-side policy, P1 licensing required); BFF sanity-checks the `amr` claim at session creation; `@RequireMfa()` decorator and freshness-based step-up are designed-in for future sensitive routes (no v1 consumer) — see [ADR-0011](decisions/0011-mfa-enforcement-entra-conditional-access.md).
|
||||
- **Identity:** multi-tenant Microsoft Entra ID with B2B invitation for workforce in v1, dual-audience design ready for future External ID activation — see [ADR-0008](decisions/0008-identity-model-entra-workforce-dual-audience.md).
|
||||
- **Authentication flow:** OIDC Authorization Code + PKCE via `@azure/msal-node`, executed entirely on the BFF; SPA never holds tokens; `__Host-` prefixed cookies, double-submit CSRF, RP-initiated logout — see [ADR-0009](decisions/0009-auth-flow-oidc-pkce-msal-node.md).
|
||||
- **Observability:** Pino structured logs + OpenTelemetry traces with W3C Trace Context propagation — to be locked-in in phase 2.
|
||||
|
||||
Reference in New Issue
Block a user