- Status: Accepted (Foundation gap noted below)
- Date: 2026-04-26
- Sub-project owner: Foundation
- Spec ref: Foundation §3, §9
The two Angular apps are Single Page Applications federated to Keycloak (dev) / ADFS (prod). The choice of OAuth2/OIDC flow and where tokens live materially affects security posture:
- Implicit flow — deprecated; tokens in URL fragment, no refresh.
- Authorization code without PKCE — vulnerable to code interception in public clients.
- Authorization code + PKCE — current best practice for SPAs.
- Token storage in
localStorage— vulnerable to XSS exfiltration. - Token storage in httpOnly cookies via a BFF (backend-for-frontend) — cookies aren't readable by JS; the BFF holds the refresh token and proxies API calls.
Target design:
- Flow: OIDC authorization code with PKCE.
- Token storage: Refresh tokens in
httpOnly,SameSite=Strict,Securecookies, scoped to the BFF origin. Access tokens injected by the BFF on every backend call. NeverlocalStorage. - BFF pattern: A lightweight backend per SPA terminates the OIDC dance, holds refresh tokens, and proxies authenticated calls.
Foundation gap:
- Foundation ships the Angular apps with
angular-auth-oidc-clientdefaults, which keep tokens in memory (not localStorage, not BFF cookies). This is acceptable for dev / Foundation scope but does not match the target design. - The BFF cookie wiring lands in sub-project 4 (External API) / 5 (Admin CMS), where the BFF endpoints are added and the SPA OIDC config is reconfigured to point at the BFF.
This ADR records the target design so future sub-projects don't drift.
- XSS no longer exfiltrates a long-lived refresh token (it's not in JS-accessible storage).
- Single-origin cookies eliminate CORS friction and CSRF risk (with
SameSite=Strict). - PKCE removes the public-client interception risk.
- BFF is one more deployable per SPA.
- Cross-origin scenarios (subdomains) require careful cookie scope.
- Sub-project 4 / 5 owns the BFF implementation.
- Foundation security gates (Trivy, CodeQL) cover the BFF when it lands.
- Rejected: XSS-prone.
- Rejected: deprecated by OAuth 2.1.
- Rejected: not safe for public clients.