Siddhant DevalAuthor
Senior Full-Stack Engineer·Oct 13, 2026·17 min read
BFF Security: Token-Mediation, OAuth Confidential Clients & CSRF Defense
The only secure way to authenticate a browser-based SPA in a microservice architecture is to keep raw OAuth tokens off the browser entirely. This article implements the Token-Mediating BFF security pattern — httpOnly session cookies, confidential OAuth client token exchange, silent refresh, and layered CSRF defenses.
Technical Series
Frontend Platform & Scale Architecture
Part 6 of 6
BFF Security: Token-Mediation, OAuth Confidential Clients & CSRF Defense
Architecture is not about drawing boxes on a whiteboard — it is about enforcing boundary contracts, deterministic caching, and secure data mediation across independent release units. Every architectural decision in this series has been about boundary clarity: which package can import which, which team owns which service, which BFF serves which client. The security boundary is the most consequential of all, because its failure mode is not a broken build or a degraded user experience — it is account compromise at scale.
The most common security mistake in browser-based SPA architectures is storing OAuth access tokens in the browser. Developers know that
localStorage is XSS-accessible. They use memory storage instead, feeling safer. Memory storage is ephemeral and survives no page refresh, so they build silent refresh timers. The timer is a JavaScript construct, and JavaScript in the browser is one XSS vulnerability away from full control. The token is still at risk.The Token-Mediating BFF pattern eliminates this attack surface entirely. No access token ever reaches the browser. The BFF is the OAuth client, the BFF holds the tokens, and the browser holds only an encrypted session cookie that is unreadable by JavaScript. This article implements the pattern end to end.
1. The Browser Token Storage Problem
1.1 The Three Broken Patterns
typescript
All three patterns share the fundamental vulnerability: the access token is a string value accessible within the JavaScript execution context. If an attacker can execute JavaScript in your application (XSS, malicious third-party script, prototype pollution), they can extract the token.
1.2 The Attack Surface: localStorage Extraction via XSS
javascript
This runs silently. No user interaction required. No browser warning. The attacker now has a valid access token with the user's full permissions, valid until it expires. If the refresh token was also stored (a common pattern), the attacker can maintain persistent access.
Performance / Safety Warning
Third-party JavaScript in your application bundle — analytics, A/B testing, chat widgets, tag managers — runs with the same origin privileges as your first-party code. A compromised third-party script is functionally equivalent to XSS. Any token in
localStorage or sessionStorage is accessible to every script on the page, regardless of origin.2. The Token-Mediating BFF Security Pattern
2.1 Architecture Overview
The Token-Mediating BFF pattern is defined by the IETF BFF Security Profile and is the current OWASP recommendation for browser-based SPA authentication in microservice architectures.
What lives where:
- Browser: Encrypted
httpOnly,Secure,SameSite=Laxsession cookie. No access token, no refresh token, no token of any kind. - BFF: Session decryption logic, access to Redis (stores
{ userId, access_token, refresh_token, expires_at }), OAuth client credentials (CLIENT_ID,CLIENT_SECRET). - Redis:
session:<session_id>→{ access_token, refresh_token, expires_at, userId }(TTL-based, expires with the session). - Auth Server: Issues tokens to the BFF as a confidential client. Never speaks to the browser directly.
2.2 OAuth 2.0 Confidential Client Registration
A confidential client is an OAuth 2.0 client that can securely hold a
CLIENT_SECRET. Server-side applications (like a BFF) are confidential clients. Browser-based SPAs cannot be confidential clients — the CLIENT_SECRET would be exposed in the bundle.3. Implementation: Login & Session Establishment
3.1 Dependencies
bash
| Package | Purpose |
|---|---|
iron-session | Encrypted, signed session cookie (AES-GCM + HMAC) |
ioredis | Redis client for server-side token storage |
@fastify/cookie | Cookie parsing for Fastify |
jose | W3C-compatible JWT validation and PKCE utilities |
3.2 Session Configuration
typescript
Crucial Requirement
The cookie stores only the
sessionId — a random identifier that maps to the real tokens in Redis. The access token never travels in the cookie. This is a critical design decision: if the session cookie were somehow decrypted (e.g., if SESSION_SECRET leaked), the attacker would only see a session ID. The actual tokens are in Redis, protected by the server's network boundary.3.3 Authorization Code Flow with PKCE
typescript
4. Token Mediation on Every API Request
4.1 The Session Middleware
typescript
4.2 Injecting Bearer Token Downstream
typescript
The access token flows: Redis → BFF middleware →
Authorization header on downstream requests. It never touches the browser at any point in this chain.
Expand
5. CSRF Defense
5.1 Why httpOnly Cookies Need CSRF Protection
httpOnly cookies solve XSS token theft. They create a new risk: CSRF (Cross-Site Request Forgery). Because the session cookie is automatically sent with every request to the BFF's origin, a malicious page on evil.example.com can trigger requests to bff.example.com and the browser will attach the session cookie.html
5.2 Defense Layer 1: SameSite=Lax
SameSite=Lax (set in the session cookie options above) prevents the cookie from being sent with cross-origin POST, PUT, DELETE requests. Top-level GET navigations still include the cookie (this is correct — loading your app from a link should work).This mitigates most CSRF vectors for form submissions and AJAX mutations.
5.3 Defense Layer 2: Origin Header Verification
typescript
5.4 Defense Layer 3: Custom Request Header
For AJAX requests from your SPA, require a custom header that simple HTML form submissions and cross-origin
fetch with no-cors mode cannot include:typescript
typescript
Pro Tip & Optimization
The triple defense (
SameSite=Lax + Origin verification + X-Requested-With) provides defense-in-depth. No single layer is perfect: SameSite=Lax has browser compatibility edge cases, Origin headers are occasionally absent in proxy setups, and custom headers can be set by browser extensions. Together, they cover each other's gaps.6. CORS Hardening
typescript
Performance / Safety Warning
Never set
Access-Control-Allow-Origin: * on a BFF that serves credentials: true (cookies). This combination is forbidden by the CORS specification and is rejected by browsers. Always use an explicit allowlist.7. Logout & Session Destruction
typescript
Session destruction is immediate: the Redis key is deleted, making the session ID in the cookie worthless. Even if an attacker had captured the encrypted cookie value, the session ID it contains resolves to nothing in Redis.

Expand
Summary
| Concept | Rule |
|---|---|
| Token storage | Access tokens never touch the browser — stored in Redis, referenced only by session ID in an httpOnly cookie |
| httpOnly cookie | Encrypted (iron-session AES-GCM), Secure, SameSite=Lax — unreadable by JavaScript |
| Session ID | The cookie holds only a session ID — the actual tokens are in Redis behind the server's network boundary |
| Silent refresh | Implemented as a server-side pre-handler interceptor on sessions expiring within 60 seconds |
| CSRF defense | SameSite=Lax + Origin header allowlist + X-Requested-With custom header |
| CORS | Explicit allowlist only — never Access-Control-Allow-Origin: * with credentials: true |
| Logout | Delete Redis key immediately — token revocation is instant regardless of JWT expiry |
Series Complete
This concludes the Frontend Platform & Scale Architecture series. The six parts build a complete stack: from workspace symlinks (Part 1) through task orchestration (Part 2), token architecture (Part 3), distribution governance (Part 4), BFF aggregation (Part 5), and BFF security (Part 6). Every architectural decision reinforces the same principle: boundary contracts enforced by tooling, not team convention.
Research & Synthesis Note
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.
#BFF#OAuth 2.0#Security#Token Mediation#CSRF#Authentication#httpOnly Cookies
Technical Series
Frontend Platform & Scale Architecture
Part 6 of 6