The problem
One general-purpose API rarely fits every client. A phone has a small screen, a slower and often metered network, and a battery to save, so it wants fewer calls and less data. A rich web page is assembled from many fine-grained endpoints, so the browser makes several round trips before it can render. An API that serves the same resources to both makes the phone over-fetch and the browser chatty.
The organisation feels it as well. Every client team needs changes in the same deployable, which is often owned by a separate API team that has to weigh the client teams' priorities against each other, and a change for one client has to be checked against all the others. Browser apps that call OAuth-protected APIs have one more problem: any token the JavaScript holds can be stolen by script injected into the page.
How it works
Give each user experience its own backend, a Backend for Frontend, owned by the team that builds that frontend. The BFF is part of the application that happens to run on the server: it exposes exactly the API its screens need and is released together with them. Its typical jobs:
- Aggregate. One client call fans out to several services, in parallel where possible, and the BFF merges the answers into one response. It also decides what happens when an optional part fails: a product page without stock levels beats no page at all.
- Shape. Return only the fields the screen shows, in the form it needs: mobile-sized image URLs, a ready-made page model for the web, the paging each client prefers.
- Translate. Hide internal protocols and service boundaries behind one stable API per client.
- Hold the session. For a browser app the BFF can also be the OAuth client, so tokens never reach JavaScript (see below).
The name comes from SoundCloud, where building every app on the generic public API meant many calls per screen and constant coordination over every endpoint change. Phil Calçado wrote up their approach in 2015, crediting the name to Nick Fisher, SoundCloud's tech lead for web, and Sam Newman's article, which also describes its use at REA, made it widely known. How many BFFs? A rule Newman quotes is one experience, one BFF: iOS and Android can share a BFF when their experiences are close and one team owns both, and get their own when they diverge or belong to different teams.
Keeping tokens out of the browser
RFC 10017, OAuth 2.0 for Browser-Based Applications (a Best Current Practice since August 2026), presents three architectures for browser apps in decreasing order of security, and the BFF comes first. Its BFF has three jobs:
- It is a confidential OAuth client: it authenticates to the authorization server and runs the authorization code flow with PKCE, so even a stolen authorization code can't be redeemed without the BFF's credentials.
- It keeps the access and refresh tokens in a cookie-based session and never exposes them to the browser.
- It forwards every API call: it removes the session cookie from the request, attaches the user's access token and sends the request on to the resource server.
The session cookie must be Secure and HttpOnly, and should be SameSite=Strict with Path=/, no Domain attribute and a name prefix such as __Host-Http-, which marks the cookie as set over HTTP and keeps it from being shared with subdomains. Script injected into the page then finds no token to steal, although it can still send requests through the BFF while the page is open, so the usual XSS defences (secure coding, a Content Security Policy) still matter. The RFC strongly recommends this architecture for business applications, sensitive applications and applications that handle personal data.
Native apps follow RFC 8252 instead: the app itself is the OAuth client (a public client, unless it gets a per-instance secret through dynamic registration) and runs the code flow with PKCE through the system browser, then typically sends its own access token to the Mobile BFF.
When to use it
- Several kinds of clients (web, iOS and Android, partners, TVs) whose screens, networks or release cycles differ, especially in front of many microservices, where most screens need data from several of them.
- Frontend teams that need to change their API as fast as their UI, without queueing behind a central API team.
- Browser apps that call OAuth-protected APIs on behalf of users, particularly with business or personal data: the BFF keeps tokens out of JavaScript.
- Not for a single web client with little to aggregate and no OAuth tokens to keep out of the browser, where a BFF is just one more hop to run; Newman suggests a web-only app needs one only when it has a lot of server-side aggregation to do. The Azure guidance also calls the pattern a poor fit when only one interface uses the backend, or when the interfaces make the same or similar requests.
- If two BFFs keep growing the same code, treat it as a signal: either the experiences are the same and one BFF will do, or the shared logic belongs in a service.
Trade-offs
- More to run. Every BFF is a service with its own pipeline, monitoring, on-call rota and patching, and each request takes one more network hop. Keep BFFs thin, stateless apart from their session store, and horizontally scaled.
- Duplication. Similar aggregation code will appear in several BFFs. Newman tolerates that rather than coupling BFFs through a shared library, and extracts a shared service (or pushes the aggregation into a downstream service) when the same logic is about to be written a third time. SoundCloud did exactly that, turning the profile-page assembly duplicated across its BFFs into a UserProfileService.
- Business logic creep. Prices, eligibility rules and authorization decisions belong in the domain services. Once they leak into BFFs, web and mobile slowly start to disagree about the same order. A BFF should only hold logic specific to its experience: composition, shaping, protocol translation and session handling.
- Fan-out failures. A BFF response depends on every service it calls. Give each downstream call a timeout, decide which parts are optional, and make sure the client can render a partial response.
- Old app versions. Mobile apps stay installed for months, so the Mobile BFF has to keep serving older versions: make changes additive or version the endpoints.
- BFF vs API gateway. An API gateway is one shared front door for cross-cutting policy (TLS, authentication, quotas, routing), usually run by a platform team. A BFF is one backend per experience, owned by a frontend team, holding client-specific composition. Chris Richardson describes the BFF as a variation of the gateway pattern, one gateway per kind of client, and the two combine well: the Azure reference example puts a gateway in front of the BFFs to handle authorization, monitoring, caching and routing.
- BFF vs GraphQL. GraphQL lets each client select exactly the fields it needs from one schema, which removes much of the over-fetching a BFF would otherwise fix; the Azure guidance notes that with frontend-specific resolvers, separate BFFs may add little. The cost moves elsewhere: the schema becomes a contract shared and governed across teams, and arbitrary queries need demand control such as depth limits, query complexity analysis or trusted documents. A GraphQL server can also be a BFF itself, and GraphQL Federation composes one graph from many services' subgraphs behind a gateway.
Implementation notes
- Build it in the frontend team's stack, often the client's own language (TypeScript for a web team, for example), and deploy it with the client.
- Fan out in parallel with a timeout per call, and pass the trace context (W3C
traceparent) on every downstream call so that one screen shows up as one trace (see Distributed Tracing). - Cache aggregated responses carefully. A reverse proxy in front of a BFF can cache them, but the expiry must be no longer than the freshest piece of data in the response allows.
- The Web BFF as an OAuth client (RFC 10017 §6.1, and the Authorization Code + PKCE flow it runs):
- Authenticate at the token endpoint with asymmetric credentials where possible (
private_key_jwtor mutual TLS, as RFC 9700 recommends), and keep access tokens short-lived and narrowly scoped. - Choose where the session lives. Server-side sessions keep the tokens on the server, as in the diagram, and allow instant revocation, but need sticky sessions, session replication or a shared store; the RFC recommends them only for small-scale deployments for that reason. Client-side sessions put the tokens in an encrypted
HttpOnlycookie that only the BFF can decrypt, so any instance can serve any request, and revoking the tokens ends access. - Refresh access tokens inside the BFF, inline when a call needs it, match the session's lifetime to the refresh token's maximum lifetime, and end the session when the refresh token stops working.
- Defend against CSRF:
SameSite=Strictis not enough if other apps share the site, so also require a custom request header (cross-origin requests with one need a CORS preflight) or use the framework's anti-forgery tokens. - Forward calls only to an allowlist of resource servers and paths, so the BFF can't be tricked into sending a user's token anywhere else.
- Tell the resource servers about the BFF: every user's calls now come from the BFF's addresses, so rate limits keyed on client IP will misfire.
- Authenticate at the token endpoint with asymmetric credentials where possible (
- Libraries: Duende.BFF for ASP.NET Core, Spring Security's OAuth 2.0 client with Spring Cloud Gateway's
TokenRelayfilter, or Curity's token handler pattern, which splits the role into an OAuth agent that issues the cookies and an API gateway plugin that swaps them for access tokens.