BFF vs SPA — where tokens live
One-line relationship: this is the architecture decision underneath sender-constrained tokens — not whether tokens are bound, but where in the browser stack they exist at all.
The problem
A browser SPA is a public OAuth client — it can’t hold a secret, and by extension it can’t hold an mTLS client certificate the way a server can. Once you’ve decided tokens need to be sender-constrained (DPoP or mTLS), you still have to decide where the token lives: in the browser’s JavaScript-accessible memory, or never in the browser at all.
Two patterns
-
SPA + DPoP — the access token sits in the browser (in memory, ideally not
localStorage), bound to a non-extractable WebCrypto key. Every API call carries the token plus a freshly signed DPoP proof. Simpler topology — no extra backend component — but the browser is now trusted to hold tokens safely, and mTLS is off the table entirely (a browser can’t present a client certificate the way DPoP lets it sign proofs). -
BFF (Backend-for-Frontend) — a server-side confidential client runs the entire OAuth flow and holds every token. The browser never sees an access or refresh token at all; it holds only an
HttpOnly,Secure,SameSitesession cookie pointing at the BFF’s own session store. The BFF calls downstream APIs on the browser’s behalf, attaching the real (possibly mTLS-bound) token itself.
Why BFF is usually the stronger bank posture
Tokens never touch the browser — the entire token-theft-from-browser risk disappears. A successful XSS injection in a SPA+DPoP architecture can still make authenticated API calls for the duration the malicious script runs (see the WebCrypto Keygen demo for why it can’t exfiltrate the key itself, but it can still ride it). In a BFF architecture, the same XSS injection has nothing to steal — there’s no token in the browser to find, only an opaque session cookie the attacker’s script also can’t read if it’s HttpOnly.
BFF also resolves the mTLS provisioning problem cleanly: the server-side confidential client can hold and present a client certificate the way a browser never could, so mTLS-bound tokens (RFC 8705) become viable again as the C2 binding mechanism, alongside or instead of DPoP.
The trade-off is architectural weight: a BFF is another server-side component to run, secure, and scale, sitting directly in the request path for every API call.
What to take to the client
For a consumer banking web channel, BFF is the default recommendation — it collapses an entire attack surface rather than mitigating it, and it unblocks mTLS as an option. SPA+DPoP remains a reasonable choice where running additional backend infrastructure isn’t feasible, but it means accepting that tokens exist in the browser and betting the whole story on the browser’s key isolation and CSP holding.