Overview
The sgivu-gateway acts as a Backend for Frontend (BFF) for the Angular application. Instead of storing OAuth2 tokens in the browser (vulnerable to XSS attacks), the gateway stores them server-side in Redis and exposes only an HTTP-only session cookie to the SPA.The BFF pattern is the recommended approach for SPAs in OAuth2/OIDC architectures, as recommended by the OAuth 2.0 for Browser-Based Apps (RFC draft).
Architecture
Key Components
- Angular SPA: Never sees access/refresh tokens, only session cookie
- Gateway (BFF): Manages OAuth2 flow, stores tokens in Redis, relays tokens to microservices
- Redis: Stores HTTP sessions containing OAuth2 tokens
- Auth Server: Issues tokens, validates credentials
- Microservices: Receive Bearer tokens from gateway, validate JWTs
Why BFF for SPAs?
Security Problems with Browser Storage
BFF Pattern Benefits
✅ XSS Protection: Tokens never exposed to JavaScript✅ CSRF Protection:
HttpOnly + SameSite=Lax cookies✅ Token Rotation: Automatic refresh token rotation
✅ Centralized Token Management: Single source of truth for session state
✅ Simplified SPA: Angular doesn’t handle OAuth2 complexity
Session Cookie Configuration
The gateway uses Spring Session with Redis to persist sessions:Cookie Settings
- Name:
SESSION - HttpOnly:
true(XSS protection) - SameSite:
Lax(allows OAuth2 redirects) - Secure: Set
truein production (HTTPS only) - Path:
/(available to all gateway routes) - MaxAge: Not set (session cookie, expires when browser closes)
Why No MaxAge?
The cookie does not define
maxAge, making it a session cookie that browsers delete on close. The actual session lifetime is controlled by Redis TTL with sliding expiration (resets on each request).If maxAge were set (e.g., 1 hour), the browser would delete the cookie exactly 1 hour after login, even if the user is active and the Redis session is still valid (sliding timeout). This causes premature logouts.Redis Session Storage
Redis is used exclusively insgivu-gateway for session persistence:
Configuration
Session Contents
Each Redis session stores:- OAuth2AuthorizedClient: Contains
access_token,refresh_token,id_token - SecurityContext: User authentication (OidcUser or JwtAuthenticationToken)
- Session metadata: Creation time, last access time, expiration
Horizontal Scaling
Redis enables stateless gateway instances:- Multiple gateway pods/containers share the same Redis
- Sessions are available to all instances (no sticky sessions needed)
- Load balancers can route requests to any gateway instance
OAuth2 Client Configuration
The gateway acts as an OAuth2 Client using Spring Security’soauth2Login():
Authorized Client Repository
Tokens are stored in the web session (backed by Redis):- Retrieves the
WebSessionfrom the exchange - Stores
OAuth2AuthorizedClient(containing tokens) as a session attribute - Persists the session to Redis automatically
BFF Authentication Flow
Initial Login
Subsequent Requests
The /auth/session Endpoint
The BFF exposes a single endpoint for the SPA to check authentication status:Implementation
TheAuthSessionController retrieves tokens from the session and decodes the JWT:
The call to
authorizedClientManager.authorize() automatically triggers token refresh if the access token is expired. Angular never needs to handle token refresh logic.Token Relay to Microservices
The gateway uses the Token Relay filter to forward the access token to backend services:Route Configuration
How Token Relay Works
- Extract Session: Get
WebSessionfrom the exchange - Retrieve Authorized Client: Load
OAuth2AuthorizedClientfrom session - Check Expiration: If access token expired, refresh it automatically
- Add Header: Set
Authorization: Bearer <access_token>on proxied request - Forward Request: Send to microservice
Automatic Token Refresh
The gateway automatically refreshes expired access tokens using the refresh token:Refresh Failure Handling
When the refresh token is invalid (invalid_grant error):
authorizedClientManager.authorize()returnsMono.empty()/auth/sessionreturns401 Unauthorized- Token relay filter skips adding
Authorizationheader - Microservices return
401for missing token - Angular detects
401and redirects to login
invalid_grant:
- Auth server restarted (authorizations lost)
- Refresh token expired (30 days)
- Token revoked (user logged out elsewhere)
- Database cleared (development)
Logout Flow
The BFF implements a 3-step logout process:Step-by-Step
Logout Success Handler
The handler uses OIDC RP-Initiated Logout when possible:- Session valid: Uses OIDC
end_session_endpointwithid_token_hint - Session expired: Falls back to
/sso-logoutendpoint (custom logout handler)
Security Considerations
CORS Configuration
The gateway allows only the Angular origin:CSRF Protection
CSRF is disabled because:- HttpOnly cookies: JavaScript cannot read/send cookies
- SameSite=Lax: Cookies not sent on cross-site POST requests
- CORS restrictions: Only Angular origin allowed
- No state-changing GET: All mutations use POST/PUT/DELETE
Session Fixation Protection
Spring Session automatically regenerates session IDs after authentication, preventing session fixation attacks.Production Deployment
Environment Variables
Redis Configuration
Production recommendations:- Use Redis Cluster or AWS ElastiCache for high availability
- Enable TLS encryption for Redis connections
- Set maxmemory-policy:
allkeys-lru(evict old sessions) - Use persistent storage (AOF or RDB) for session recovery
Scaling Considerations
- Gateway Instances: Scale horizontally (stateless with Redis)
- Redis: Use cluster mode for multi-AZ deployment
- Session Timeout: Balance security (shorter) vs UX (longer)
- Token Refresh: Happens automatically, no manual intervention
Related Documentation
- OAuth2 & OIDC - Authorization server configuration
- JWT Tokens - Token structure and validation
- Service Communication - Internal service authentication