Cookie-Only Sessions Implementation Plan
Status: Phases 1-4 complete; Phase 5 rollout tracked in docs/epics/auth/README.md
Created: 2025-12-18
Goal: Eliminate JS-readable tokens (accessToken in response body / localStorage) in favor of httpOnly cookie-based authentication.
Overview
Current state: IDP returns tokens in JSON response body, and frontends store them in memory/localStorage and attach via Authorization: Bearer header.
Target state: IDP sets httpOnly cookies, frontends never see tokens, backends extract JWT from cookies.
Implementation Status
Phase 1: IDP Changes ✅ COMPLETE
Files:
apps/idp/src/app/controller/auth.controller.tsapps/idp/src/app/services/cookie-manager.service.tsapps/idp/src/app/dto/cookie-only-auth-response.dto.ts(new)
Implemented:
-
Token delivery mode header ✅
X-Token-Delivery: cookie | jsonAffects endpoints:
POST /api/auth/login✅POST /api/auth/refresh✅POST /api/auth/mfa/verify-login✅
-
Cookie mode behavior ✅
- Returns
{ ok: true }instead of tokens in JSON body - Sets httpOnly access cookie
idp_atwithPath=/api - Continues setting
idp_rt/idp_jtiwithPath=/api/auth - CSRF mechanism (
XSRF-TOKEN+X-XSRF-Token) unchanged
- Returns
-
Cookie path changes ✅
idp_at(access token):Path=/apiso all API routes receive itidp_rt/idp_jti(refresh):Path=/api/auth(unchanged)
-
Tests ✅
apps/idp/src/app/controller/__tests__/auth.token-delivery.cookie.spec.ts- Test runner guard for TCP listen() blocked environments:
apps/idp/src/app/test-utils/can-listen.ts
Not yet implemented:
- OAuth redirect token delivery (still uses URL fragments)
Phase 2: libs/auth Changes ✅ COMPLETE
Files:
libs/auth/src/lib/strategies/jwt.strategy.ts
Implemented:
-
JWT extraction from cookies ✅
jwtFromRequestnow accepts either:Authorization: Bearer <token>header (existing)idp_atcookie (new)
- Priority: Authorization header first, then cookie (see
libs/auth/src/lib/strategies/jwt.strategy.ts)
-
Tests ✅
libs/auth/src/lib/strategies/__tests__/jwt.strategy.onmoduleinit.spec.tslibs/auth/src/lib/strategies/__tests__/jwt.strategy.spec.ts
Phase 3: libs/services/client/idp-client Changes ✅ COMPLETE
Files:
libs/services/client/idp-client/src/client.ts
Implemented:
tokenDelivery: 'cookie'option setswithCredentials: true- Adds
X-Token-Delivery: cookieon login/refresh/MFA verify - Refresh flow supports cookie-only responses (no token persistence)
- CSRF header wiring uses
XSRF-TOKEN→X-XSRF-Token
Phase 4: VoicePro Migration ✅ COMPLETE (first consumer)
Backend: apps/voicepro/voicepro-backend
- Cookie JWT extraction now works via
@digiwedge/auth(no bearer token required)
Frontend: apps/voicepro/voicepro-frontend
- Cookie-only mode enabled (
tokenDelivery: 'cookie') - Legacy token persistence removed on boot
- Auth provider uses cookie refresh + profile fetch
Phase 5: Rollout Tracking
Phase 5 rollout status and app-level tracking live in docs/epics/auth/README.md.
Cookie Specification
| Cookie | Purpose | Attributes |
|---|---|---|
idp_at | Access token (JWT) | httpOnly, Secure, SameSite=Strict, Path=/api |
idp_rt | Refresh token | httpOnly, Secure, SameSite=Strict, Path=/api/auth |
idp_jti | Token ID for refresh | httpOnly, Secure, SameSite=Strict, Path=/api/auth |
XSRF-TOKEN | CSRF token (readable) | Secure, SameSite=Strict, Path=/ |
Domain considerations:
- Dev proxy setup
- Prod FQDNs (
.digiwedge.com) - Cross-subdomain auth (if needed)
How to Use (Opt-in)
Web clients send X-Token-Delivery: cookie header on:
POST /api/auth/loginPOST /api/auth/refreshPOST /api/auth/mfa/verify-login
Response will be { ok: true } instead of token JSON. Tokens are delivered via httpOnly cookies.
Default behavior unchanged: Without the header, IDP returns JSON tokens (mobile/legacy clients unaffected).
Local Development Notes
Cookie-only auth relies on Secure cookies, so browsers will drop them on http://localhost or any non-HTTPS origin. If your web app is running over HTTP, the idp_at/idp_rt cookies will not be stored and /api/auth/profile will 401.
Recommended options:
- Use the HTTPS dev proxy (
*.dev.digiwedge.com) so Secure cookies persist. - For local HTTP development, set
VITE_IDP_TOKEN_DELIVERY=jsonso the client receives tokens in JSON instead of cookies.
Migration Checklist
- IDP: Add
X-Token-Deliveryheader support - IDP: Implement cookie-only response mode
- IDP: Update cookie-manager for access cookies (
idp_at) - IDP: Add tests for cookie mode
- IDP: Fix OAuth fragment token delivery
- libs/auth: Add cookie JWT extractor
- libs/auth: Add extraction tests
- idp-client: Support cookie-only mode
- VoicePro backend: Verify cookie auth works
- VoicePro frontend: Remove token persistence
- VoicePro: E2E test cookie-only flow
- Update hooks-auth-web for cookie-only mode
- Phase 5 rollout: See
docs/epics/auth/README.md
Security Considerations
- CSRF Protection - Already in place, must remain for state-changing endpoints
- SameSite Cookies - Use
StrictorLaxdepending on cross-site requirements - Secure Flag - Required for production (HTTPS only)
- Cookie Scope - Careful with
PathandDomainto prevent leakage - Token Rotation - Refresh must rotate both access and refresh cookies
References
- Cookie handling:
apps/idp/src/app/services/cookie-manager.service.ts - JWT strategy:
libs/auth/src/lib/strategies/jwt.strategy.ts - IDP client:
libs/services/client/idp-client/src/client.ts - Auth hooks:
libs/hooks/hooks-auth-web/src/lib/useRehydrateTokens.ts - Cookie mode tests:
apps/idp/src/app/controller/__tests__/auth.token-delivery.cookie.spec.ts
Document History
| Date | Change |
|---|---|
| 2025-12-18 | Initial planning document |
| 2025-12-18 | Phase 1 (IDP) + Phase 2 (libs/auth) implemented and validated |
| 2025-12-22 | VoicePro cookie-only migration complete; idp-client + hooks-auth-web updated |
| 2025-12-29 | Align Phase 3-4 status with repo and cookie-only client behavior |