Skip to content
Customer Identity · available nowBuild your first Test integration.Create a Test project
NamoID
All posts
NamoID Blog

RFC 10017: OAuth for Browser-Based Applications

Apply RFC 10017 to browser OAuth: choose a BFF, token-mediating backend, or browser client and secure cookies, PKCE, tokens, CSRF, and APIs.

For a business application, sensitive product, or application handling personal data, RFC 10017 points you toward a backend-for-frontend (BFF): keep OAuth tokens on a confidential server and give the browser a protected session cookie. A browser-only OAuth client remains possible, but the RFC explicitly does not recommend that architecture for those higher-risk applications.

The important change is not another reminder to use Proof Key for Code Exchange (PKCE). RFC 10017 became an IETF Best Current Practice in August 2026 and gives browser applications a complete attacker model, three architecture patterns, and implementation requirements for each. This guide turns those requirements into a decision you can apply to a React, Next.js, Vue, Angular, or similar frontend.

RFC 10017 defines three supported architectures

RFC 10017 describes three ways to connect a browser application to an OAuth authorization server and protected APIs. They differ in where tokens live and which component performs the OAuth flow.

PatternOAuth clientTokens visible to browser JavaScriptPrimary trade-off
Backend-for-frontendConfidential backendNoStronger containment; backend proxy and cookie security required
Token-mediating backendConfidential backendAccess token onlyRefresh-token containment; access tokens remain exposed
Browser-based OAuth clientPublic browser applicationAccess and possibly refresh tokensLeast infrastructure; largest browser attack surface

This is a security hierarchy, not a maturity model. A browser-only application does not become a BFF by moving token exchange into one serverless function. If access tokens still return to JavaScript and the browser calls resource APIs directly, the design is a token-mediating backend.

Malicious JavaScript can do more than read storage

The RFC assumes an attacker can execute JavaScript in the application's origin through cross-site scripting, a compromised dependency, a malicious package, or another code-injection path. Under that assumption, arguing about localStorage versus in-memory storage addresses only one attack.

Malicious code can:

  • steal the tokens currently available to the application;
  • wait and steal every rotated token;
  • start a new authorization flow inside the user's active session;
  • call application functions or alter their behavior; and
  • proxy requests through the user's browser without stealing a credential.

This is why “we keep tokens in memory” is not a complete browser security design. Memory can reduce persistence after a page closes, but code executing in the same origin can often read the value, call the function that returns it, or request a new token. RFC 10017 treats the browser execution context—not a particular storage API—as the trust boundary.

Short token lifetimes and refresh-token rotation still matter. They reduce exposure and can detect some replay. They do not defeat malicious code that remains present and continuously captures the newest token.

A BFF keeps OAuth tokens out of JavaScript

In the BFF pattern, the backend is a confidential OAuth client. It starts the Authorization Code flow, exchanges the code, stores the resulting tokens, and calls resource APIs for the browser. The browser receives only a session cookie.

Browser              BFF              Authorization server       API
   |                  |                         |                  |
   |-- start login -->|-- authorization ------>|                  |
   |<----------- redirect to sign-in ----------|                  |
   |------------ authorization response ------>|                  |
   |                  |-- code + credentials -->|                  |
   |                  |<------- tokens ---------|                  |
   |<-- session ------|                         |                  |
   |-- app request -->|-- access token -------------------------->|
   |<-- response -----|<------------------------------------------|

The browser cannot read an HttpOnly cookie through ordinary JavaScript, and the access and refresh tokens never enter the browser runtime. That blocks direct token exfiltration. It does not make cross-site scripting harmless: malicious code may still submit requests through the victim's active BFF session.

RFC 10017 requires a BFF to act as a confidential client and use the Authorization Code grant. Its cookie guidance is unusually concrete:

  • enable Secure and HttpOnly;
  • prefer SameSite=Strict;
  • use the / path;
  • avoid a Domain attribute; and
  • use an HTTP-set cookie prefix such as __Host-Http- where supported.

A representative response header looks like this:

Set-Cookie: __Host-Http-session=<opaque-value>; Path=/; Secure; HttpOnly; SameSite=Strict

The value should be an opaque session identifier or protected session state. Do not put a plaintext refresh token into a readable cookie and call it a BFF.

Moving tokens to a backend replaces browser token handling with session and proxy responsibilities. The BFF must implement a deliberate cross-site request forgery (CSRF) defense for state-changing requests.

SameSite=Strict is useful, but it is not a universal CSRF strategy. Subdomains under the same registrable domain are “same site” even though they are different origins. A takeover or injection at old.example.com can therefore matter to app.example.com. Depending on the deployment, use strict origin checks, a required custom header with a narrow Cross-Origin Resource Sharing policy, or a synchronizer-token pattern in addition to cookie settings.

The proxy also needs a fixed upstream policy:

  • map known frontend routes to known API destinations;
  • never accept an arbitrary target URL;
  • strip untrusted forwarding and credential headers;
  • attach a token only to its intended audience;
  • constrain methods, content types, and body sizes; and
  • enforce authorization again at the resource API.

The BFF is a credentialed security boundary. Treating it as a generic reverse proxy can turn token containment into server-side request forgery or credential leakage.

A token-mediating backend is the middle pattern

A token-mediating backend performs the Authorization Code flow as a confidential client and keeps refresh tokens on the server. When the frontend needs an access token, it requests one through its cookie-authenticated backend session and then calls the API directly.

This removes refresh tokens and client credentials from JavaScript. It also blocks malicious code from exchanging a stolen authorization code because the attacker lacks the confidential client's credential. But the architecture intentionally exposes access tokens to the browser, so an injection can still steal them and call resource servers directly.

RFC 10017 recommends evaluating a full BFF first. Use token mediation when direct browser-to-API calls are a real system requirement and the full proxy pattern is not viable—not merely because returning a token is easier than designing a safe proxy.

Be careful with cached tokens. If the frontend asks for orders:read, the backend should not return a cached token carrying orders:read orders:refund. Request the smallest available scopes and resource audience. A mediation endpoint that hands every frontend request the broadest cached token silently defeats least privilege.

A browser-only client is a constrained choice

In a browser-only architecture, JavaScript is the public OAuth client. It redirects to the authorization server, receives the code, exchanges it using PKCE, stores tokens, and calls APIs directly.

Use Authorization Code with S256 PKCE. Do not use the Implicit grant: RFC 10017 says authorization servers must not issue access tokens in the authorization response and browser clients must not use Implicit. PKCE binds the authorization code to the client instance that created the verifier, but it does not protect an access token after hostile JavaScript obtains it.

If a browser-only design is necessary:

  1. Keep access-token scope and lifetime narrow.
  2. Restrict tokens to the intended API audience.
  3. Use refresh-token rotation or sender-constrained tokens when the authorization server and API support them.
  4. Avoid persistent token storage where possible.
  5. Apply a restrictive Content Security Policy and reduce third-party script execution.
  6. Treat every dependency and tag manager as code running inside the OAuth client.
  7. Enforce resource ownership and business policy at the API.

DPoP can bind a token to a key held by the client and make a copied token harder to replay elsewhere. It does not stop malicious code already running in the application from using the key or asking the legitimate client to make requests. Sender constraint is defense in depth, not a cure for a compromised origin.

Same-domain applications may not need OAuth to call themselves

If one backend serves the frontend and its own API under a common application boundary, do not introduce access tokens solely to replace a server session. RFC 10017 notes that OpenID Connect can handle federated login while the application maintains ordinary server-side session state for its own frontend-to-backend interaction.

OAuth becomes useful when the architecture has independent resource servers, third-party or delegated access, multiple domains, or clients that need portable authorization. Using it where those boundaries do not exist can add token lifecycle and validation work without creating a meaningful security boundary.

The distinction is:

  • use OpenID Connect to establish who signed in;
  • use an application session for a same-application browser and backend; and
  • use OAuth access tokens when a client calls a separately protected resource.

Do not send an ID token to an API as a substitute. The access-token versus ID-token guide explains why their audiences and consumers differ.

Choose the architecture with a decision tree

Start with the resource and risk rather than the frontend framework:

Does the browser call an independent protected API?
|
+-- No --> Use OIDC for login if needed, then a server-side application session.
|
+-- Yes --> Can you operate a backend that mediates API calls?
            |
            +-- Yes --> Use a BFF. Keep tokens server-side.
            |
            +-- No --> Must the browser call the API directly?
                       |
                       +-- Yes, but a confidential backend is possible
                       |      --> Consider token mediation; keep refresh tokens server-side.
                       |
                       +-- Browser must own the full OAuth flow
                              --> Use a public client with Code + S256 PKCE,
                                  narrow tokens, and explicit acceptance of the risk.

For business applications, sensitive actions, or personal data, the answer should normally stop at the BFF branch. The BFF versus SPA comparison covers the operational trade-offs in more detail.

Audit an existing browser application

Use this review before changing libraries:

  • Identify exactly which component acts as the OAuth client.
  • Trace authorization codes, access tokens, refresh tokens, ID tokens, and cookies end to end.
  • Confirm that browser flows use Authorization Code with S256 PKCE.
  • Remove Implicit and password-grant flows.
  • Check whether tokens are persisted in browser storage.
  • Record every third-party script running in the application origin.
  • Verify exact redirect URI registration and issuer validation.
  • Verify that each API requires its own audience and necessary scopes.
  • Test CSRF defenses on every cookie-authenticated state-changing route.
  • Test XSS consequences without assuming token storage is the only target.
  • Confirm logout, revocation, refresh rotation, and session expiry behavior.
  • Decide whether the current risk justifies moving from browser-only to token mediation or a BFF.

Architecture changes should follow this trace. Replacing one OAuth library while preserving the same token exposure does not change the trust boundary.

NamoID supports the protocol boundary, not the architecture decision

NamoID uses hosted authentication and the Authorization Code flow with mandatory S256 PKCE. It publishes OIDC discovery and JWKS metadata, and applications register exact redirect URIs. Those authorization-server controls support all three RFC 10017 patterns.

Your application still chooses where tokens live, how a local session is protected, whether a BFF proxies API calls, and how the resource server enforces audience, scopes, tenant boundaries, ownership, and business policy. An identity provider cannot make those application decisions from outside the architecture.

For a new browser product handling customer or personal data, start with a BFF unless a concrete constraint rules it out. Use the NamoID developer resources to connect the authorization flow, then apply the RFC 10017 checklist to the application boundary around it.