Skip to main content

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.ts
  • apps/idp/src/app/services/cookie-manager.service.ts
  • apps/idp/src/app/dto/cookie-only-auth-response.dto.ts (new)

Implemented:

  1. Token delivery mode header

    X-Token-Delivery: cookie | json

    Affects endpoints:

    • POST /api/auth/login
    • POST /api/auth/refresh
    • POST /api/auth/mfa/verify-login
  2. Cookie mode behavior

    • Returns { ok: true } instead of tokens in JSON body
    • Sets httpOnly access cookie idp_at with Path=/api
    • Continues setting idp_rt/idp_jti with Path=/api/auth
    • CSRF mechanism (XSRF-TOKEN + X-XSRF-Token) unchanged
  3. Cookie path changes

    • idp_at (access token): Path=/api so all API routes receive it
    • idp_rt/idp_jti (refresh): Path=/api/auth (unchanged)
  4. 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:

  1. JWT extraction from cookies

    • jwtFromRequest now accepts either:
      • Authorization: Bearer <token> header (existing)
      • idp_at cookie (new)
    • Priority: Authorization header first, then cookie (see libs/auth/src/lib/strategies/jwt.strategy.ts)
  2. Tests

    • libs/auth/src/lib/strategies/__tests__/jwt.strategy.onmoduleinit.spec.ts
    • libs/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 sets withCredentials: true
  • Adds X-Token-Delivery: cookie on login/refresh/MFA verify
  • Refresh flow supports cookie-only responses (no token persistence)
  • CSRF header wiring uses XSRF-TOKENX-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.


CookiePurposeAttributes
idp_atAccess token (JWT)httpOnly, Secure, SameSite=Strict, Path=/api
idp_rtRefresh tokenhttpOnly, Secure, SameSite=Strict, Path=/api/auth
idp_jtiToken ID for refreshhttpOnly, Secure, SameSite=Strict, Path=/api/auth
XSRF-TOKENCSRF 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/login
  • POST /api/auth/refresh
  • POST /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=json so the client receives tokens in JSON instead of cookies.

Migration Checklist

  • IDP: Add X-Token-Delivery header 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

  1. CSRF Protection - Already in place, must remain for state-changing endpoints
  2. SameSite Cookies - Use Strict or Lax depending on cross-site requirements
  3. Secure Flag - Required for production (HTTPS only)
  4. Cookie Scope - Careful with Path and Domain to prevent leakage
  5. 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

DateChange
2025-12-18Initial planning document
2025-12-18Phase 1 (IDP) + Phase 2 (libs/auth) implemented and validated
2025-12-22VoicePro cookie-only migration complete; idp-client + hooks-auth-web updated
2025-12-29Align Phase 3-4 status with repo and cookie-only client behavior