Auth0 to OpenIddict Migration — Revised Phase Plan¶
Strategy Change¶
Previous approach: BFF only talks to OpenIddict; Auth0 stays as direct JWT bearer. Revised approach: BFF with Auth0 first, then swap BFF backend to OpenIddict.
This separates two independent risks: 1. "Does the BFF pattern work?" (cookie-based auth, session management, Angular changes) 2. "Does OpenIddict work as an IdP?" (user management, claims, Google OAuth, email flows)
Each is independently testable and reversible.
Architecture¶
Angular knows nothing about the identity provider. It authenticates via cookies and /api/auth/* endpoints. The API's BFF controller handles all OIDC — which IdP it talks to is purely server-side config (BffAuth:Authority).
┌─────────────┐ cookie ┌─────────────┐ OIDC ┌─────────────────┐
│ Angular │ ────────► │ API (BFF) │ ───────► │ Auth0 (Phase 2) │
│ (SPA) │ │ /api/auth │ │ or │
│ │ ◄──cookie─ │ /api/* │ │ OpenIddict (3+) │
└─────────────┘ └─────────────┘ └─────────────────┘
Issues Identified¶
Based on the IETF draft-ietf-oauth-browser-based-apps-26 and current implementation review:
1. CSRF Protection (MUST FIX before production BFF)¶
The BFF spec requires CSRF protection on state-changing endpoints. Our POST /api/auth/logout and POST /api/auth/refresh have no CSRF defence. Options:
- CORS-based (recommended by IETF): restrict BFF endpoints to same-origin requests via CORS headers. Our API already has CORS configured — verify BFF endpoints are covered.
- Double-submit cookie: set a non-HttpOnly CSRF token cookie, Angular reads it and sends as a header, API validates.
- SameSite=Lax helps but is not sufficient alone.
2. Session Scalability¶
InMemorySessionStore doesn't work across multiple API replicas. The IETF draft notes server-side sessions are "only recommended in small-scale scenarios." For SyRF (~5,700 users, typically <100 concurrent), in-memory is fine for initial rollout, but Redis or encrypted cookie sessions are needed before scaling.
3. Auth0 Confidential Client Required¶
To use BFF with Auth0, we need a confidential client (server-side, with client_secret) registered in Auth0. The current syrf-web Auth0 application is a SPA (public) client. Need to either:
- Register a new M2M/Regular Web Application in Auth0 for the BFF, or
- Change the existing application type
The BFF callback URL (/api/auth/callback) also needs adding to Auth0's allowed callback URLs.
4. Angular Provider Simplification¶
The authProvider config switch (auth0 | oidc | bff) is migration scaffolding. In the BFF world, Angular has one auth mechanism: cookies + /api/auth/*. The switch and the Auth0AuthProvider/OidcAuthProvider classes should be removed in Phase 5. Document this as intentional tech debt.
5. No API Proxying Needed¶
The pure BFF pattern proxies all resource API requests through the BFF. In our case, the API service IS the BFF (same origin), so Angular calls the API directly with cookies. This is correct and simpler than a separate proxy. No change needed — just documenting the decision.
Revised Phases¶
Phase 1: BFF Infrastructure (THIS PR — complete)¶
Status: ✅ Done, pending merge
What's in PR #2416:
- BFF auth controller (/api/auth/*) with OIDC auth code + PKCE
- Session store (in-memory) + SessionAuthenticationHandler
- Combined auth scheme (BFF cookie + JWT bearer fallback)
- Dev auth + E2E auth controllers
- Angular BffAuthProvider + HTTP interceptor
- Identity Service foundation (OpenIddict, Helm chart, CI detection)
- Auth0 fallback for production safety
- Auth0 custom actions exported for migration reference
Phase 2: BFF + Auth0 in Production (1-2 weeks)¶
Goal: Switch Angular from direct Auth0 SPA tokens to BFF cookies, with Auth0 still as the OIDC backend.
Prerequisites:
- [ ] Register Auth0 confidential client for BFF (Regular Web Application type)
- [ ] Add /api/auth/callback to Auth0 allowed callback URLs
- [ ] Configure BffAuth:Authority = Auth0 custom domain
- [ ] Add CSRF protection to BFF state-changing endpoints
Tasks:
- [ ] Set Angular to authProvider: 'bff' in staging
- [ ] Verify full login flow: Angular → /api/auth/login → Auth0 → callback → cookie → app
- [ ] Verify logout, session refresh, /api/auth/me
- [ ] Run E2E tests with BFF through mock-oauth2-server
- [ ] Verify all existing functionality works with cookie auth
- [ ] Roll out to production (set BffAuth:Enabled=true, Angular authProvider: 'bff')
- [ ] Monitor login success rates, error rates
- [ ] Keep JWT bearer as fallback for any clients not yet on BFF
Verification: - All users authenticate via BFF cookies - Auth0 is the IdP (no OpenIddict involved yet) - No user-facing behaviour change (login page is still Auth0)
Phase 3: Identity Service Deployment + Parallel Auth (2-3 weeks)¶
Goal: Deploy OpenIddict Identity Service, run it alongside Auth0.
Tasks:
- [ ] Complete Identity Service: Register/Password Reset pages, email verification flow, profile completion, conflict detection (#2428, #2431)
- [ ] Replicate Auth0 custom action logic in OpenIddict (see docs/planning/auth0-actions-openiddict-mapping.md)
- [ ] CI pipeline: version job, build matrix, Docker image publishing (#2430)
- [ ] cluster-gitops: ArgoCD ApplicationSet, DNS, secrets
- [ ] Deploy Identity Service to staging
- [ ] Configure staging API with BffAuth:Authority = Identity Service
- [ ] Test full BFF flow against OpenIddict in staging
- [ ] Verify feature parity with Auth0 path
- [ ] E2E tests against both Auth0 and OpenIddict BFF backends
Verification: - Identity Service running in staging - BFF works with both Auth0 (production) and OpenIddict (staging) - Same Angular code, different server-side config
Phase 4: User Migration + Gradual Rollout (2-4 weeks)¶
Goal: Import Auth0 users to OpenIddict, gradually switch production BFF from Auth0 to OpenIddict.
Tasks: - [ ] Export Auth0 users (5,711) including Google identity links (#2437) - [ ] Import to OpenIddict preserving syrf_id mappings - [ ] Password reset campaign via SES (#2438) - [ ] Gradual rollout: internal → beta → all (#2439) - [ ] Monitor login success rates >99% - [ ] Google OAuth users verify seamless login (same client credentials)
Verification: - All users imported with correct data - Production BFF pointed at OpenIddict - Login success rate >99%
Phase 5: Cutover + Cleanup (1 week)¶
Goal: Remove Auth0 code, simplify Angular to BFF-only.
Tasks:
- [ ] Remove Auth0AuthProvider, OidcAuthProvider from Angular
- [ ] Remove authProvider config switch — BFF is the only path
- [ ] Remove Auth0Service, AuthManagementApiClientProvider from API
- [ ] Remove Auth0 NuGet packages, npm packages
- [ ] Remove JWT bearer fallback from combined auth scheme
- [ ] Update DNS: signin.syrf.org.uk → Identity Service
- [ ] Keep Auth0 read-only 4 weeks, then decommission (#2441, #2442)
Verification: - Zero Auth0 references in codebase - Angular has one auth mechanism (cookies) - Auth0 subscription cancelled
Timeline¶
| Phase | Duration | Depends on |
|---|---|---|
| Phase 1 (this PR) | Done | — |
| Phase 2: BFF + Auth0 | 1-2 weeks | Phase 1 merged |
| Phase 3: Identity Service | 2-3 weeks | Phase 2 verified |
| Phase 4: User migration | 2-4 weeks | Phase 3 deployed |
| Phase 5: Cutover | 1 week | Phase 4 complete |
Total remaining: 6-10 weeks
Key Risks¶
| Risk | Impact | Mitigation |
|---|---|---|
| CSRF attacks on BFF endpoints | High | Add CSRF protection before Phase 2 production rollout |
| Session store doesn't scale | Medium | Redis implemented, conditional on config — deploy Redis alongside API |
| Auth0 confidential client misconfigured | High | Test in staging before production; keep JWT fallback |
| Google OAuth re-consent on IdP change | Medium | Reuse same client credentials (Decision D007) |
| syrf_id mapping breaks during import | Critical | Verify with subset import first; rollback plan |
| BFF adds latency to login flow | Low | BFF is same-origin on API server; no extra hop |
| Third-party JWT clients break | Medium | JWT bearer stays in combined scheme permanently (D014) |
Multi-Client Authentication¶
The API serves two types of clients today, with a third planned:
- Angular SPA — BFF cookies via
/api/auth/*. Angular never holds tokens. - Machine clients — API keys via the existing
ApiKeyauth scheme. Used for automation, server-to-server. - Third-party OIDC clients (future) — JWT bearer tokens. Not implemented yet — will be added when the public API for external researchers is built. Adding it now would be premature.
The combined policy scheme after Phase 2:
The JWT bearer scheme exists today for the Auth0 migration period only. It will be removed when Angular switches to BFF (Phase 2 complete), and re-added later if/when third-party OIDC clients are needed.