feat(portal-bff): entra config foundation — boot validator + auth module (#102)
## Summary First step of ADR-0009 wiring on the BFF: capture the Entra app-registration env vars in the boot pipeline so subsequent PRs can plug `@azure/msal-node` onto a typed, already-validated config without re-reading `process.env`. **No MSAL client, no OIDC routes, no session integration yet** — those land in follow-up PRs. ## What lands - **[`.env.example`](apps/portal-bff/.env.example)** promotes the Entra block from its previous "future-vars" comment stub to an active section. Six keys: - `ENTRA_INSTANCE_URL` — the Microsoft login endpoint (e.g. `https://login.microsoftonline.com/`). - `ENTRA_TENANT_ID`, `ENTRA_CLIENT_ID`, `ENTRA_CLIENT_SECRET` — the values from the Entra app-registration UI. - `ENTRA_REDIRECT_URI`, `ENTRA_POST_LOGOUT_REDIRECT_URI` — consumed by the OIDC routes in a follow-up PR. Multi-tenant `ENTRA_ACCEPTED_TENANT_IDS` stays in the future-vars comment until External ID activation (ADR-0008 phase 2). - **[`apps/portal-bff/src/config/check-entra-config.ts`](apps/portal-bff/src/config/check-entra-config.ts)** — boot-time validator mirroring `check-database-url.ts`. Verifies every required key is present, the instance URL is `https://` and ends with `/`, tenant + client IDs are UUIDs, none of them are the literal placeholder values from `.env.example`, and the two redirect URIs parse as URLs. Returns a typed `EntraConfig` object with a pre-computed `authority` field (`${instanceUrl}${tenantId}`) so the future MSAL factory does not re-derive it. - **[`auth.module.ts`](apps/portal-bff/src/auth/auth.module.ts)** — `AuthModule` whose v1 surface is one provider: the parsed `EntraConfig` keyed by the `ENTRA_CONFIG` injection token. Factory delegates to `assertEntraConfig()`. Non-global on purpose — consumers state intent by importing the module. - **Bootstrap wiring** — `main.ts` calls `assertEntraConfig()` alongside `assertDatabaseUrl()` so misconfiguration fails fast at boot rather than mid-request (per ADR-0018 §"BFF env-var loading"). `AppModule` imports `AuthModule`. ## Naming choice Chose `ENTRA_*` rather than `AZURE_AD_*` to align with the ADR text (Microsoft Entra ID, post-2023 rebrand). The values you copy from the Entra app-registration UI go into `apps/portal-bff/.env` (git-ignored). ## Decisions worth flagging - **Validator called twice** — once in `main.ts` (boot-time fail-fast) and once in the `AuthModule` factory (to obtain the value for DI). Both reads are idempotent and trivially cheap. The duplication is intentional: boot-time gives a clear, pre-NestFactory error; the factory call surfaces the typed value to consumers. - **No `@azure/msal-node` dependency added yet** — introducing the dep without a consumer would be a smell. Lands in the next PR alongside the MSAL client factory. - **Pre-computed `authority`** in the parsed config rather than letting each MSAL consumer concatenate `instanceUrl + tenantId`. One place to change if the multi-tenant authority (`/organizations`, `/common`) replaces the tenant-scoped one when External ID activates. ## Verification - `nx run-many -t lint test build --projects=portal-bff` — green. - **29 / 29 specs** (was 20; +9 from the new entra-config spec + auth.module spec). - Boot smoke test (manual): with the placeholder values in `.env.example`, `nx serve portal-bff` aborts immediately with `ENTRA_CLIENT_ID is still the .env.example placeholder (…)`. With real values in a local `.env`, the BFF starts normally. ## Test plan - [x] Lint + test + build green. - [x] Validator unit-test covers happy path + every documented failure mode. - [ ] Manual: drop the real Entra values you obtained into `apps/portal-bff/.env`, `nx serve portal-bff` boots clean. - [ ] Manual: temporarily blank out one of the four `ENTRA_*` keys → BFF aborts at boot with a clear message naming the missing key. ## Next PRs on the auth track 1. Install `@azure/msal-node`, add the `MsalConfidentialClient` factory provider in `AuthModule`, expose it via DI. 2. First OIDC routes: `/api/auth/login` (PKCE-initiated redirect to Entra) + `/api/auth/callback` (token exchange + ID-token validation, audit-logged, no session persistence yet). 3. Session persistence per ADR-0010 (Redis + AES-GCM, `__Host-portal_session` cookie). Closes the auth loop. 4. RP-initiated logout, CSRF protection, route guards. --------- Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr> Reviewed-on: #102
This commit was merged in pull request #102.
This commit is contained in:
@@ -34,16 +34,42 @@ OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
|
||||
# Collector (per ADR-0012). Override only for spike investigations.
|
||||
OTEL_TRACES_SAMPLER=always_on
|
||||
|
||||
# Identity / Entra ID app registration (per ADR-0008 / ADR-0009)
|
||||
# Values come from the project's Entra application registration in the
|
||||
# Azure Admin Center → App registrations → APF Portal. The four
|
||||
# *_INSTANCE_URL / *_TENANT_ID / *_CLIENT_ID / *_CLIENT_SECRET keys
|
||||
# are mandatory; the BFF refuses to boot without them (see
|
||||
# apps/portal-bff/src/config/check-entra-config.ts).
|
||||
#
|
||||
# ENTRA_INSTANCE_URL is the Microsoft login endpoint — usually
|
||||
# https://login.microsoftonline.com/. The authority used by MSAL is
|
||||
# `${ENTRA_INSTANCE_URL}${ENTRA_TENANT_ID}` for single-tenant flows,
|
||||
# or `${ENTRA_INSTANCE_URL}organizations` / `common` for multi-tenant
|
||||
# (per ADR-0008's dual-audience design). v1 uses the tenant-scoped
|
||||
# authority; the multi-tenant switch lands when External ID activation
|
||||
# is needed.
|
||||
#
|
||||
# ENTRA_CLIENT_SECRET is the high-value secret of this set. Never
|
||||
# commit a real value. Production manages it via the deploy platform's
|
||||
# secret manager (future infrastructure ADR).
|
||||
ENTRA_INSTANCE_URL=https://login.microsoftonline.com/
|
||||
ENTRA_TENANT_ID=00000000-0000-0000-0000-000000000000
|
||||
ENTRA_CLIENT_ID=00000000-0000-0000-0000-000000000000
|
||||
ENTRA_CLIENT_SECRET=replace_with_real_value
|
||||
# Redirect URIs registered in Entra alongside the same client id. Both
|
||||
# `/auth/callback` and `/auth/logout` paths are mounted by the BFF
|
||||
# once the OIDC routes land in a subsequent PR.
|
||||
ENTRA_REDIRECT_URI=http://localhost:3000/api/auth/callback
|
||||
ENTRA_POST_LOGOUT_REDIRECT_URI=http://localhost:4200/
|
||||
|
||||
# Future env vars introduced by upcoming phases / ADRs:
|
||||
#
|
||||
# Auth flow (ADR-0009):
|
||||
# ENTRA_TENANT_ID
|
||||
# ENTRA_CLIENT_ID
|
||||
# ENTRA_CLIENT_SECRET (or ENTRA_CLIENT_CERT_PATH)
|
||||
# ENTRA_ACCEPTED_TENANT_IDS
|
||||
# ENTRA_REDIRECT_URI
|
||||
# ENTRA_POST_LOGOUT_REDIRECT_URI
|
||||
# SESSION_SECRET
|
||||
# Auth flow (ADR-0009) — additional keys wired as the routes land:
|
||||
# ENTRA_CLIENT_CERT_PATH (alternative to ENTRA_CLIENT_SECRET)
|
||||
# ENTRA_ACCEPTED_TENANT_IDS (CSV; restricts which tenants can sign in
|
||||
# in the multi-tenant phase — empty means
|
||||
# "only ENTRA_TENANT_ID is accepted")
|
||||
# SESSION_SECRET (cookie signing key, see Sessions below)
|
||||
#
|
||||
# Sessions (ADR-0010):
|
||||
# REDIS_URL (or REDIS_SENTINEL_HOSTS + REDIS_SENTINEL_NAME)
|
||||
|
||||
Reference in New Issue
Block a user