June 8, 2026
Secure Client-Side Architecture Series — Part 2
Securing High-Stakes PWAs via the BFF Pattern, Runtime Cryptography, and Advanced Anti-CSRF
Tech with Arian
7 min read
Securing High-Stakes PWAs via the BFF Pattern, Runtime Cryptography, and Advanced Anti-CSRF
In Part 1, we discussed all our browser storage options and how attacks like XSS can undermine the security of some of these options completely. We learned that if JavaScript can touch a high-value authentication secret, that secret is vulnerable to theft via Cross-Site Scripting (XSS) or disk exfiltration via host-level Infostealers. For high-stakes applications, such as enterprise Progressive Web Apps (PWAs), relying on client-managed JSON Web Tokens (JWTs) is an existential security risk.
To achieve true defense-in-depth, we must radically shift our paradigm: we must remove identity secrets from JavaScript's reach entirely. In this part, we will discuss how to decouple token management from the presentation layer using the Backend-for-Frontend (BFF) pattern, which might be a very bold strategy to design our architecture. This whole design is inspired by OAuth 2.0 for Browser-Based Applications IETF Draft.
Well, it might sound like overkill for a simple SPA application, but we are trying to design an ultimate secure architecture, and our goal is to cover the most sensitive platforms and enterprise applications, which might deal with hundreds of microservices behind the scenes and use OAuth to handle their authentication needs.
And of course, you can design your own application in lots of different ways just by knowing the attack surface of each browser storage option (which you can master by reading the previous part) and understanding your own requirements and how much risk you are willing to accept.
What is a Backend-for-Frontend (BFF)?
Historically, Single Page Applications (SPAs) and PWAs communicated directly with downstream microservices or a centralized Identity Provider (IdP) via OAuth 2.0 / OIDC public client flows. In this model, the browser acts as the direct recipient of Access Tokens and Refresh Tokens.
A Backend-for-Frontend (BFF) is an architectural pattern that introduces a dedicated, highly secure server-side abstraction layer built explicitly to support a specific client user interface. Instead of the client app talking directly to complex backend microservices, the PWA communicates exclusively with the BFF.
The BFF acts as a stateful security proxy and a reverse proxy. It shields the client application from the underlying infrastructure, standardizes API payloads, and — most importantly — takes over the responsibility of authentication and session management on behalf of the browser runtime.
But What's the difference between BFF and a simple backend service?
The BFF pattern was popularized by SoundCloud and Netflix to solve a specific problem: mobile apps, desktop apps, and smart TVs all have wildly different user interfaces, screen sizes, and performance constraints, but they often consume the same data.
Instead of forcing every single frontend application to talk to the same general-purpose backend API, you build a dedicated, lightweight backend layer for each specific user interface.
BFFs essentially do these:
- Data Aggregation: If a mobile screen needs data from three different microservices (e.g., User Profile, Recent Orders, and Notifications), the mobile app makes one single request to its BFF. The BFF calls the three microservices internally, stitches the data together, and sends it back to the mobile app.
- Data Formatting & Trimming: A desktop site might need a massive JSON payload with 50 fields, but a mobile app might only need 5 fields. The BFF filters out the unnecessary data to save user bandwidth and battery.
- Security & Token Management: In modern web applications, a BFF is often used to handle authentication securely (like implementing the BFF Security Pattern for OAuth 2.0/OIDC), holding sensitive access tokens in secure server-side sessions rather than exposing them to the browser's JavaScript.
Now that we are familiar with BFF and its roles, let's answer the more important question.
Why We Use a BFF in Our Architecture
We transition to a BFF architecture to solve the core architectural flaws of client-side token storage. By implementing a BFF, we achieve two critical security objectives:
- Elimination of XSS-Based Token Theft: By handling the OAuth handshake entirely on the server side, raw Access and Refresh Tokens never enter the browser's JavaScript memory or local disk storage. If an XSS vulnerability occurs on the UI layer, there are no raw tokens in the browser storage for an attacker to exfiltrate.
- Protection of Sensitive Client Credentials: Public client apps cannot safely store OAuth client secrets (
client_secret). A BFF, running in a secure server-side container, can safely hold confidential environment variables, allowing the application to utilize more secure, authenticated OAuth client profiles.
As I said in the last part, the whole mindset is that the browser is not a safe place to store sensitive data.
Our BFF Architecture & Session Flow
Our architecture decouples the untrusted client runtime from the backend network. The ecosystem consists of three distinct layers:
- PWA Frontend
- Stateful BFF Proxy
- Downstream Identity Provider (IdP) & Microservices.
The Authentication Sequence
- The Handshake: The user inputs their credentials into the PWA interface. The frontend transmits them directly to the BFF over a secure TLS connection.
- Token Exchange: The BFF intercepts the credentials, appends the payload with its server-side
client_idand confidentialclient_secret, and executes a secure back-channel token request to the upstream Identity Provider. - Server-Side Token Retention: The IdP validates the request and returns the raw
access_tokenandrefresh_token. The BFF does not pass these to the browser. Instead, it generates an opaque, cryptographically secure random session string and caches the raw tokens inside an isolated server-side session store (such as a secure Redis instance or encrypted database), mapped directly to that session string. - Issuing the Hardened Cookie Perimeter: The BFF issues a standard HTTP response back to the PWA, attaching the opaque session string within an ultra-hardened cookie using the maximum security specification
Set-Cookie: __Host-SessionID=opaque_random_string_here; Secure; HttpOnly; SameSite=Strict; Path=/; Max-Age=28800Set-Cookie: __Host-SessionID=opaque_random_string_here; Secure; HttpOnly; SameSite=Strict; Path=/; Max-Age=28800By forcing the browser to enforce the __Host- prefix rules, we eliminate domain hijacking and cookie-tossing vectors from sibling subdomains, while HttpOnly renders the session entirely invisible to JavaScript execution threads.
The Data Classification Matrix
While the BFF handles identity tokens, a PWA still needs to store operational data locally to maintain UI states and support robust offline functionality. To prevent developers from accidentally dumping high-risk data into insecure buckets, we enforce a strict Data Classification Matrix:
Anti-CSRF Infrastructure: Neutralizing the Subdomain Threat
While the __Host- prefix and SameSite=Strict attributes provide top-tier defenses against standard cross-site tracking and cookie manipulation, they are not a silver bullet in complex corporate enterprise environments. We must defend against a highly dangerous edge case: Subdomain Hijacking coupled with XSS.
The Vulnerability: Cross-Subdomain Scripting
Imagine your secure PWA runs on https://app.example.com. A secondary, legacy marketing site runs on https://promo.example.com. If an attacker compromises the secondary site via a subdomain takeover or an XSS vulnerability, they gain an active footprint within the overarching Same-Site boundary (example.com).
Because browsers treat subdomains under the same eTLD+1 as "Same-Site", an attacker executing scripts inside promo.example.com can bypass traditional cross-origin blocks. If they trick a user into visiting the compromised subdomain, they can write scripts that perform top-level navigations or use cross-origin windows to force a state-changing action against https://app.example.com. Because the browser sees this interaction as originating from within the broader "Same-Site" family, it may attach your session cookies, completing a Cross-Site Request Forgery (CSRF) attack despite standard defenses.
The Mitigation: The Anti-CSRF Token
To completely neutralize this vector, our BFF architecture implements a mandatory, cryptographically enforced Anti-CSRF validation for all state-changing operations:
- Token Generation & Delivery: Upon a successful authentication handshake, the BFF generates a highly random, cryptographically secure anti-forgery token tied directly to the user's server-side session. This token is sent to the client strictly within the HTTP response JSON body (e.g., inside a
/user/profileor/session/handshakepayload). - Volatile Client Storage: The PWA frontend extracts this token from the response body and holds it exclusively inside an In-Memory JavaScript State Manager (or wrapped within a secure closure). It never touches
localStorageor the physical disk, protecting it from Infostealers. - The Dynamic Interceptor Loop: The frontend configures a global network interceptor (via
Axiosor a customfetchwrapper). For every state-changing request, the interceptor automatically reads the in-memory token and appends it as a custom HTTP header:
X-CSRF-Token: cryptographically_secure_random_stringX-CSRF-Token: cryptographically_secure_random_string- Strict BFF Verification: When a request hits the BFF, the proxy inspects the incoming headers. If the request is a state-changing method, the BFF extracts the
X-CSRF-Tokenheader and compares it directly against the token stored inside the user's server-side session cache. If the header is missing, malformed, or mismatched, the BFF drops the request instantly and returns a403 Forbidden. Idempotent requests (GET,HEAD,OPTIONS) are safely exempted.
Because a hijacked sibling subdomain (promo.example.com) cannot read the in-memory JavaScript variables or response bodies of your primary application origin (app.example.com) due to strict Same-Origin Policy (SOP) blocks, the attacker can never obtain or forge this custom header. The CSRF attack vector is rendered entirely unexploitable.
The Final Picture
By using these architectural components together, we achieve an incredibly resilient client-server ecosystem:
- The Browser UI Layer is stripped of structural privileges. It holds no long-lived identity tokens and writes no raw secrets to the operating system's hard drive, eliminating the impact of automated XSS exfiltration and Infostealer database sweeps.
- The BFF Layer acts as an ironclad firewall. It manages complex token handshakes, maps sessions to random, opaque strings, isolates raw tokens in a high-trust backend cache, and validates every inbound state transition through independent cryptographic header checks.
What's Next?
Our session tokens are now safely out of reach, and our backend validation perimeter is secure against cross-site forgery. However, our architecture is still only as strong as the containment of our client-side runtime environment. If an attacker can inject malicious code, they can still manipulate the UI or abuse our application as a live proxy.
In Part 3: Hardening the PWA Runtime, we will focus on exploring absolute runtime containment. We will deep-dive into:
- Strict Content Security Policies (CSP): Designing maintainable, robust policies utilizing cryptographic
noncesand the modernstrict-dynamicdirective to kill inline script execution entirely and using trusted types to force the browser engine to block all string-based assignments to dangerous DOM sinks (likeinnerHTML), allowing only pre-validated, cryptographically approved objects. - Advanced Injection Mitigations (Context-Aware Encoding & Structural Sanitization): Moving beyond basic string manipulation by implementing robust, multi-layered sanitization frameworks (like DOMPurify) and context-aware escaping rules. This ensures that any user-controlled data injected into the application runtime is strictly parsed as harmless data strings, completely neutralizing the browser's ability to interpret malicious inputs as executable JavaScript, structural HTML tags, or stealthy CSS styling overrides.
- The Non-Extractable Web Crypto Ledger: Providing the exact modern JavaScript implementation code to generate hardware-bound symmetric keys (
extractable: false) within IndexedDB, allowing safe, encrypted offline PWA performance without exposing raw encryption keys to the runtime thread.