The BFF: Put Token Authority Behind the Server

RFC 10017’s Backend for Frontend pattern answers the browser-token question by moving the OAuth client itself behind a server boundary. The key issue is not whether the browser receives a cookie; it is whether the browser application ever possesses the access or refresh tokens, and whether it can redeem a new authorization code with the credentials of the OAuth client. This chapter traces the BFF’s flow and then examines the controls, costs, and residual risks that determine whether the boundary is actually trustworthy.

What the BFF actually owns

A BFF is a server-side component that belongs to the frontend application. That description distinguishes it from a generic gateway or reverse proxy. In RFC 10017’s model, the BFF becomes the OAuth client for the browser application. It has three connected responsibilities: it interacts with the authorization server as a confidential client, manages access and refresh tokens as part of a cookie-based user session, and forwards the frontend’s resource requests with the appropriate access token.

The distinction between a BFF and a browser-only client begins with client type. A browser application’s source is delivered to the end user, so a secret placed in that source cannot be treated as confidential. The BFF, by contrast, runs on a server-side component that can establish credentials with the authorization server. Its ability to authenticate as a confidential client is the property that changes the fresh-token attack, not the mere fact that some code has been moved to another process.

The login flow makes the ownership boundary visible. When the browser loads the application, the application first checks the BFF for an active session. If none exists, the browser navigates to a BFF endpoint that starts an Authorization Code flow with PKCE. The BFF redirects the browser to the authorization endpoint. After the user’s authorization-server session completes, the browser returns an authorization code to the BFF. The BFF exchanges that code at the token endpoint using its client credentials and the PKCE verifier. The browser has carried the redirect, but it has not become the party that redeems the code for OAuth tokens.

The BFF then associates the resulting access token and optional refresh token with the user’s session and sets a cookie that identifies that session. In the server-side session form, that cookie is only a handle from the browser’s point of view and the OAuth tokens remain on the server. RFC 10017 also allows client-side session state: token material may be carried in a signed, optionally encrypted cookie. With the required HttpOnly protection, frontend JavaScript cannot read that cookie, and encryption is recommended when it contains access tokens. In neither form does the browser application receive an access token to attach directly to each resource request. When the frontend reloads, it checks the BFF again and can learn that it is authenticated, along with suitable identity information.

For an ordinary API operation, the same separation continues. The browser sends a request to the corresponding BFF endpoint with its session cookie. The BFF uses the session to recover the user’s OAuth state, removes the cookie from the outbound request, and attaches the access token when it forwards the request to the resource server. The resource-server response returns through the BFF to the browser application. A user can experience this as one authenticated action, but the BFF, rather than frontend JavaScript, performs the OAuth-authorized request.

flowchart LR B[Browser application] S[Session cookie] F[BFF confidential OAuth client] T[(Access and refresh tokens)] A[Authorization server] R[Resource server] B -->|navigate to login| F F -->|redirect| A A -->|authorization code| F F -->|token exchange with credentials and PKCE| A F -->|associate| T F -->|set| S S -->|session request| B B -->|cookie plus API request| F F -->|allowlisted request with access token| R R -->|response| F F -->|application response| B

Refresh tokens remain part of the same server-side ownership model. When the BFF sees that an access token has expired, it can use the associated refresh token to obtain a fresh access token, authenticating as the confidential client. This can happen while handling a frontend API call or when the BFF observes an expiration event. If the refresh token expires, the BFF cannot create a valid access token from it; a new Authorization Code flow is required. Linking the cookie-session lifetime to the maximum refresh-token lifetime, and invalidating a session when its refresh token is no longer valid, keeps the session’s apparent availability aligned with the authority it represents.

Session state itself can be held in different ways. A server-side session can expose only a session identifier to the browser while keeping all data on the server. That gives operators strong control over active sessions and the ability to revoke them, but it can complicate scaling through sticky sessions or session replication; the RFC describes this arrangement as recommended for small-scale BFF scenarios. A client-side session can place a signed, optionally encrypted object in the cookie, reducing the server’s session-state burden but making active-session control and revocation more difficult. In the BFF case, revocation still follows from the access and refresh tokens associated with the session. If a client-side cookie contains access tokens, encrypting its contents is recommended so the tokens are not written to local persistent storage in plaintext. Encryption protects confidentiality from someone who can read the cookie’s stored bytes; it does not stop someone who can use the encrypted cookie from attempting session hijacking.

The BFF is therefore a translation point with state, not a storage trick. It translates a browser session into OAuth authority for a bounded resource request. Its security benefit comes from making the server the OAuth client, keeping token use and refresh operations behind it, and preventing frontend JavaScript from directly reading or attaching those tokens. The confidential-client credentials remain server-side, while a client-side session still requires the cookie protections and encryption described above.

The browser still acts, but not as the OAuth client

The BFF changes which attack scenarios are available, but it does not make arbitrary code in the browser harmless. If malicious code runs in the application’s origin, it can still send requests to the BFF from the user’s browser. The BFF’s session cookie is protected from direct JavaScript access when it is HttpOnly, so the attacker cannot simply read the cookie value through the script API. Yet the attacker can attempt to make the application perform actions through the same session. The BFF removes direct token extraction and public-client code redemption; it does not remove client hijacking.

That division is important when interpreting the RFC’s strong recommendation. A BFF effectively mitigates direct single-execution and persistent theft by frontend JavaScript because a properly protected BFF does not expose the tokens to the browser application’s execution context; with a server-side session they remain on the server, while a client-side session relies on protected cookie state and has separate at-rest limits. It also mitigates acquisition of a fresh token set through the public frontend because a code observed in the browser cannot be exchanged as the BFF without the BFF’s client credentials. PKCE still protects the authorization code transaction, but the more decisive boundary is that the browser does not possess the confidential-client authentication needed for the backend’s token request.

The remaining browser-origin attack is proxying. Malicious code can call a BFF endpoint just as legitimate frontend code can. If the endpoint accepts a request and the user’s session authorizes it, the BFF may make the corresponding protected-resource call. The BFF can reduce the consequences through server-side policy: it can expose only the operations the frontend needs, apply authorization checks, observe traffic, and enforce rate limits or anomaly restrictions. But it cannot assume that every request arriving from the authenticated browser was caused by the intended button click. Client-side code execution remains a condition that must be prevented with secure coding, code analysis, CSP, careful third-party-resource choices, and related defense in depth.

A session cookie also introduces a different class of obligation: CSRF. The browser automatically includes a matching cookie on requests, so a malicious site may try to cause the browser to submit a request to the BFF even though the attacker cannot read the cookie. RFC 10017 therefore requires the BFF to implement a proper CSRF defense. The exact mechanism depends on the deployment’s site and origin relationships.

The cookie’s baseline properties are not optional decoration. The BFF must enable Secure so the cookie is sent only over protected transport, and it must enable HttpOnly so frontend scripts cannot read it directly. SameSite=Strict is recommended, as are a root cookie path, omission of the Domain attribute, and a prefix indicating that the cookie was set through HTTP, such as __Host-Http-. Together with HTTPS, these settings counter direct cookie-session attacks and reduce the risk of subdomain-based session fixation or hijacking. The distinction from a JavaScript-readable token cookie is fundamental: the BFF cookie is meant to be sent to the backend as session state, not read by the application and copied into an Authorization header.

SameSite is not a complete answer in every domain arrangement. It limits cross-site requests, but two different subdomains can be same-site because they share an effective top-level domain plus one label while remaining cross-origin. If another application on that site is compromised or taken over, it may be able to create a cross-origin request that is still treated as same-site for cookie purposes. A BFF hosted alongside other applications therefore needs to reason about the full site, not just whether the frontend and backend have different hostnames.

CORS can provide another CSRF defense because a cross-origin script request with a non-safelisted shape normally requires a preflight. If the BFF refuses the attacker’s origin in the preflight response, the browser blocks the actual request, and a request sent from the attacker’s own machine will not carry the user’s cookie. But CORS does not preflight every possible request. A GET or a form-compatible POST may be sent without a preflight, with the browser restricting access to the response only afterward. If a state-changing endpoint accepts such a request, the side effect may already have happened. RFC 10017 therefore recommends requiring a custom request header when relying on CORS and requires the BFF to ensure that every incoming request carries that static header. A framework’s correctly applied anti-forgery or double-submit-cookie mechanism can serve as an alternative.

The other server-side boundary is outbound request control. Because the BFF translates a session cookie into an access token, an endpoint that lets callers influence the destination can become a token-exposure mechanism. The BFF must allowlist approved resource-server hosts and paths and should restrict HTTP methods for each endpoint. If dynamic routing is necessary, the validation must still guarantee that a caller cannot turn an ordinary frontend operation into a request to an arbitrary server. Otherwise, “the token never enters the browser” would be offset by “the server can be persuaded to send it anywhere.” The BFF is more like an application-layer reverse proxy than a transparent HTTP proxy, so its routes need explicit meaning and destination policy.

This boundary also changes privacy and operations. Since every resource request and response passes through the BFF, the BFF can observe the traffic between the browser application and the resource server. If the same party builds and operates both, the added privacy exposure may be limited by the existing trust relationship. If the BFF is supplied or hosted by a third party, that visibility is a material part of the architecture decision. Similarly, the resource server needs to understand that requests from many users may arrive from one BFF address. Rate limiting by source IP can otherwise treat a shared backend as one abusive client and block legitimate users together.

The BFF can be deployed in several practical arrangements: it can serve the static frontend itself, sit beside a separate static host or content-delivery service, or combine with other serverless components. Those deployment choices do not change the central relationship, but they can affect CORS, session scaling, monitoring, and the number of operators who can observe the request path. The security recommendation is strongest when the BFF is treated as a carefully bounded frontend component with clear ownership of tokens, routes, session policy, and logs—not as an unexamined gateway inserted between two existing systems.

The BFF’s conclusion is consequently precise. It removes direct access to OAuth tokens from the browser application and prevents the browser from acting as the confidential client, so the most damaging token-theft and fresh-redemption scenarios are no longer available through frontend JavaScript. A client-side session may still carry protected token material in the browser, so its at-rest and session-hijacking boundaries must be stated rather than silently treated as server-only storage. The browser remains capable of hijacking the client and asking the BFF to perform authorized operations. The price of the stronger boundary is a server component that must defend cookies, CSRF, destinations, session state, privacy, and operational capacity. That is a substantial price, but for sensitive applications it buys a change in attacker capability that no isolated browser variable can provide.

References