Backend for Frontend: the layer that knows who is asking
The pattern was described in 2015 for the sake of speed and team autonomy. Eleven years later an IETF draft started strongly recommending it for something its authors never wrote about — keeping the token out of the browser entirely.
Full technical treatment
TL;DR
- A BFF is a small server that stands between your screen and the rest of the services and works for that one screen.
- The first reason to have one: collect the data for a screen in one call instead of six. Every call from the browser costs time, and those times add up.
- The second reason, which appeared much later: the session key stays on the server and never reaches the browser at all.
- But a BFF does not save you from the page itself being broken into. It decides what an attacker takes away with them, not whether they can do damage right now.
The problem it all grew out of
Picture a profile screen. To draw it you need the user's own data, their bookmarks, their reading progress and a couple of smaller things. If the browser asks for this piece by piece, it makes several separate calls to the server — and waits for an answer to each one.
While you are developing on your own laptop, the waiting is close to zero: the server is on the same machine. On a real network it is different, especially over mobile. And here the unpleasant part shows up: the times are not “about the same,” they add up.
What it actually costs
Below is a measurement on a real application. The 150 ms of latency on every request is imposed deliberately: without it the measurement would show that the network is free, and that is exactly the illusion that needs breaking.
6 requests from the browser, sequentially
900 ms
6 × 150 ms. The latency is paid once for every call made.
one request to the BFF, 6 inside
154.3 ms
150 ms + 6 × 0.71 ms. The second term is the measured median of one call to the Go service from the same machine.
One request — 165 ms. Six requests — 972 ms. Nearly a second, during which the server did nothing complicated: the browser simply went there and back six times.
The BFF idea in one sentence: let the browser make one call, and let all six internal calls be made by a server that is a stone's throw from the neighboring services. In that same measurement, a call to a neighboring service took less than a millisecond.
An important caveat: if your screen is assembled with one request, there is nothing to assemble and you do not need a BFF for this reason — you would be adding an extra step and winning nothing.
The second reason: the key to the door
There is a second reason to have such a layer, and it is about something else entirely.
An ordinary application that calls the API itself has to keep the session key — the token — somewhere. And it has to keep it where JavaScript can reach: in localStorage or in an ordinary cookie. Which means someone else's script will reach it too, if it manages to get onto the page.
A BFF makes that unnecessary. The browser receives not a token but a cookie marked HttpOnly — one that JavaScript cannot read at all. The real token stays on the server, and the server attaches it to the onward requests itself.
Here is a measurement of what a script sees under three ways of storing it. Switch between the probes with the buttons.
> localStorage.getItem("access_token")
Token in localStorage
the SPA calls the API itself
leaked
"tok_1f3a9c8e2b7d4a06"
The very token that was put there.
Token in a cookie without HttpOnly
the SPA calls the API itself
unreachable
null
It is not in localStorage — it is in the cookie.
Session in an HttpOnly cookie behind a BFF
the browser knows only the BFF
unreachable
null
Measured: the application's localStorage is empty, there are no keys at all.
HttpOnly. Note the third probe: the “behind a BFF” column differs from the others only in that the script has no token — it gets the data anyway.And now the uncomfortable truth
Switch the visualization to the third probe — the one where the script asks for the bookmarks.
It gets them. It has no token, and it has the data.
The reason is simple: the script executes on your own page, which means that as far as the browser is concerned it is you. The browser obediently attaches the cookie to its requests, the way it would to yours.
Hence a rule worth remembering in exactly this wording:
A BFF changes not the probability of a break-in but its consequences. Stealing the key and walking away with it is no longer possible. Making mischief in your name while the tab is open still is.
The difference here is not cosmetic. A stolen token works after you have closed the tab, from anywhere in the world, and without you. Access that lasts “while the tab is open” ends together with the tab.
What else is mandatory if the session lives in a cookie
The draft IETF standard that describes this approach requires three things, and not one of them is optional in a real application:
- the cookie travels only over a secure connection;
- the cookie is unavailable to JavaScript;
- separate protection against request forgery — the cookie's own properties do not cover this.
The last point is often skipped. There is a setting called SameSite that asks the browser not to attach the cookie to requests coming from other sites — and it is often taken to be sufficient. The cookie specification says the opposite in plain words: it is a useful extra layer, but not a complete defense. So the server additionally issues a one-time marker — a token that only your page knows — and checks it on every action that changes something.
You can check this right in the measurement above: a request with a valid cookie but without such a token is refused.
When you do not need a BFF
Briefly: when there is nothing to collect and nothing to hide.
If you have one frontend, one team, one service behind it, the screen is assembled with one request, and there are no tokens in the browser for other reasons — you would be adding an extra service, an extra deployment and an extra step in the chain. Sam Newman, the author of the canonical description of the pattern, calls this out separately: for an application with a web interface only, such a layer is justified only if there really is something to aggregate on the server.
And the other way round: as soon as a screen gets its own team with its own rate of change — or as soon as something ends up in the browser that has no business being there — the layer starts paying for itself.
TL;DR
- A BFF is not a layer between the frontend and the backend “in general.” It is a separate backend per user experience, owned by the team that owns that experience.
- It was invented for two things: to remove round-trip latency and to remove the queue for changes to a shared API. About where the token is kept the original texts say nothing at all.
- The pattern's second life began in the standards: the IETF draft on OAuth in the browser strongly recommends the BFF, because with it the token never physically reaches JavaScript.
- What the BFF does not do: it does not save you from XSS. A script executing in your origin will still read the user's data — just with someone else's hands. That is written down in the draft itself.
Why know this?
Because “BFF” has become the word for anything at all between the browser and the services: a proxy, a gateway, “our api route in Next.js.” As a result, a conversation about architecture turns into an argument about names, and decisions taken from an article about an entirely different problem end up in the code.
The pattern has authors, publication dates and two different reasons to exist, a decade apart. The first is performance and team autonomy: 2012 at Netflix, 2015 at SoundCloud. The second is token storage, and it came not from architects but from an IETF text. Understanding which of the two is yours matters more than knowing the definition: it determines how many BFFs you stand up, who owns them, and what counts as failure.
The mental model
Picture an interpreter at a negotiation. A universal API is a dictionary: it honestly translates every word, but to understand a sentence you have to look into it seven times, and the dictionary has no idea you are negotiating a supply contract. The interpreter does: they listen to the whole sentence, check whatever needs checking, and hand you one finished answer — in your language and in your context.
The key part of this metaphor is whose interpreter it is. They were hired by your side and they leave with it. That is the defining property of a BFF: it belongs to the frontend team, not to the platform team, and it changes with the screen rather than with the domain model.
Where this actually came from
The story starts not with the BFF but with Netflix. In July 2012 Daniel Jacobson described why their one-size-fits-all REST API had stopped working: the company supported, in his words, more than 800 device types, and one and the same response suited all of them equally badly. The conclusion in that article is put bluntly — such an API is convenient for whoever serves it, not for whoever consumes it.
Netflix solved this not with “one more endpoint” but by changing who owned the code: the UI teams got the right to write server-side adapters for their own screens. Six months later Ben Christensen, in the follow-up, described the scale — 2+ billion incoming requests a day — and the main win: assembling a screen in a single call, so that the WAN latency is paid once.
Notice what is absent from those two texts. Neither of them ever discusses where the token is kept. They are articles about latency and about who writes the code.
The name itself appeared in September 2015: Phil Calçado, an engineer at SoundCloud, described their path — a monolith, then a public API for all clients at once, then three walls. The third wall is the most underrated: every new endpoint took a long discussion to keep it from turning out too narrow for one particular screen. For the new iOS application the team stood up a separate backend — “different backends for different frontends.”
Two months later Sam Newman turned this into a pattern — and preserved for us the rule that gets quoted most often. He himself credits it to Stewart Gleadow, who in turn credits Phil Calçado and Mustafa Sezgin:
One experience, one BFF.
Read it literally. Not “one per frontend,” not “one per team,” not “one per application.” The unit of division is the experience: if iOS and Android are nearly identical, one BFF is enough for both; once they have diverged, you need two. Alongside it Newman quotes Pete Hodgson's observation: in practice BFFs map best onto team boundaries, and it is the structure of the teams that should determine how many there are — because what you divide up is what somebody then has to maintain.
The first reason: round trips add up
The 2012 argument looks obvious right up until you measure it. In development the network feels free, because on localhost it very nearly is.
Below is a measurement. A real browser against a running application, with 150 ms of latency per request imposed through the Chrome DevTools Protocol; every number is the median of three runs. The dashed line and the tiles are arithmetic over your numbers, not a measurement; the sliders are there so you can substitute your own.
6 requests from the browser, sequentially
900 ms
6 × 150 ms. The latency is paid once for every call made.
one request to the BFF, 6 inside
154.3 ms
150 ms + 6 × 0.71 ms. The second term is the measured median of one call to the Go service from the same machine.
What matters here is not the fact that “six requests take longer than one” but what the difference turns into. Six sequential calls at 150 ms is 972 ms: nearly a second during which nothing complicated happened on the server. The same screen behind a single call to a BFF costs one round trip plus the work inside the datacenter, where a call to a neighboring service came to 0.71 ms at the median of twenty samples.
Hence an important caveat that retellings usually drop. Newman puts it cautiously — “I suspect” — but plainly enough: for an application with a web interface only, a BFF is justified only if there really is something to aggregate on the server. If your screen is one request to one service, you get an extra network hop and an extra deployment, and nothing in exchange.
The second reason arrived a decade later
Security does get mentioned in the 2015 texts — but a different kind of security. For Calçado it is the first of three problems with a universal API: OAuth scopes had to be checked everywhere, and forging the keys of the “official application” had to be made difficult. That is about who is allowed to call an endpoint, not about where the token is kept. The token-storage argument came later, and from somewhere else — from standardization.
The IETF draft “OAuth 2.0 for Browser-Based Applications” (revision 27, July 2026; handed to the IESG and awaiting publication as a BCP) examines three architectures in decreasing order of security, puts the BFF first and strongly recommends it for business applications, sensitive applications and anything that handles personal data. It defines the BFF's duties in three points: the BFF acts as a confidential OAuth client, keeps access and refresh tokens inside a cookie session, and attaches the right token to requests to the resource server itself.
The point of the whole construction the draft states in a single sentence: the tokens are available to the BFF only, and there is nothing in the browser to extract.
That is a checkable claim, not a declaration. Below is a measurement of what a script executing on the page can reach, for three ways of storing a session. The “behind a BFF” column is the verbatim output of a run against a live stand (Next.js in production mode in front of a Go service in front of PostgreSQL); the other two were taken the same way on a separate page, where the same token was put into localStorage and into a cookie without HttpOnly.
> localStorage.getItem("access_token")
Token in localStorage
the SPA calls the API itself
leaked
"tok_1f3a9c8e2b7d4a06"
The very token that was put there.
Token in a cookie without HttpOnly
the SPA calls the API itself
unreachable
null
It is not in localStorage — it is in the cookie.
Session in an HttpOnly cookie behind a BFF
the browser knows only the BFF
unreachable
null
Measured: the application's localStorage is empty, there are no keys at all.
HttpOnly. Note the third probe: the “behind a BFF” column differs from the others only in that the script has no token — it gets the data anyway.The first two probes confirm exactly what was promised, and along the way they explain why OWASP's Session Management Cheat Sheet flatly forbids putting tokens and session identifiers into localStorage or sessionStorage: there they are visible to any script in your origin.
And the third probe is the most useful thing in this article.
What the BFF does not do
Switch the visualization above to the third probe. A script that has no token calmly reads the user's bookmarks: it simply makes a fetch to its own origin, and the browser attaches the cookie.
This is not an implementation defect. The IETF draft names the attack outright — proxying requests through the user's browser, section 5.1.4: the malicious code executes in the same context as the legitimate application and can therefore reproduce its behavior. In the discussion of the BFF (section 6.1.4.1) this scenario remains the only one the pattern does not close, and the same place says it: nothing can prevent it except not letting foreign code execute in the first place.
The practical conclusion, worth saying out loud at any architecture review:
The BFF changes the consequences of XSS, not the probability of XSS. The attacker can no longer steal a long-lived token and walk off with it into their own infrastructure. Acting as the user for as long as the tab is open — that they still can do.
The difference is real and large: a stolen refresh token works after the tab is closed, from another country, without the victim. But selling the BFF as “protection from XSS” means promising something other than what it does.
What the standard requires of the cookie
Once the session has moved into a cookie, the requirements on that cookie stop being a matter of taste. The draft states them in sections 6.1.3.2 and 6.1.3.3, in the normative vocabulary of RFC 2119:
| Requirement | Level | What it means |
|---|---|---|
Secure | MUST | The cookie never travels over plain HTTP |
HttpOnly | MUST | The cookie is invisible to document.cookie |
SameSite=Strict | SHOULD | The cookie is not attached to cross-site requests at all |
| Protection against CSRF | MUST | A separate mechanism, not replaceable by the previous row |
The last two rows are worth separating, because they get confused constantly. SameSite is browser behavior, not a check on your server. rfc6265bis says so in section 5.6.7.1 without hedging: Lax mode is a reasonable defense in depth against CSRF attacks that use unsafe methods, but it is not a complete defense against CSRF as a class of attacks. OWASP states the same thing as a rule: treat SameSite as one layer of depth and combine it with a CSRF token rather than relying on it alone.
Hence the synchronizer token pattern: the server issues a token bound to the session and, on every state-changing request, compares the value that arrived with the one it stored. A cross-site form can make the browser attach the cookie, but it cannot read a response from your origin — and so it never learns the token.
An honest deviation: why Lax here and not Strict
The application these measurements were taken on sets SameSite=Lax. The draft says SHOULD ... Strict. This is a deliberate deviation, and here is the reason for it.
Email confirmation and password reset links arrive by mail. Following such a link is a top-level cross-site navigation: the message is open in a mail client, not on your site. Under Strict the browser will not attach the cookie to that navigation, and a person who has just confirmed their address lands on a page that treats them as a guest. Under Lax the cookie is attached to top-level navigations made with safe methods — that is, to exactly the click from the email, but not to a cross-site POST.
SHOULD in RFC 2119 means precisely this situation: the requirement may go unmet if you have understood the consequences. The consequence here is a weakened first line of defense against CSRF, and it is compensated by the second: a synchronizer token with a constant-time comparison, plus a check of the Origin header. The probe “state-changing request without the CSRF header” in the measurement above is exactly that: a request from the same origin, with a valid cookie but without the token, gets a 403.
What matters methodologically: a deviation from a SHOULD stops being a problem exactly when it is written down together with its reason and with whatever covers it. An unrecorded deviation is simply a bug nobody knows about yet.
How many BFFs to run, and what they cost
The rule “one experience, one BFF” sounds simple and breaks on the second BFF. Newman does not hide the problem: duplication accumulates between BFFs. What he advises against is merging them back into a “shared aggregating edge service”: such a service, in his observation, turns time after time into bloated code with mixed-up concerns. Which is to say, the same universal API people were escaping from, under a new name.
About the duplication itself he is relaxed: “I am fairly relaxed about duplicated code across services”. This is not a ban on extracting shared code — Newman lists both a shared library and a separate service as options — but he warns that a library is the main source of coupling, especially if clients for the downstream services are generated through it. The practical reading: duplication between BFFs is a price paid deliberately, and shared code is worth extracting once it has settled, not at the first coincidence.
A practical frame: a BFF is justified when a screen has its own rate of change and its own team. If you have one frontend, one team and one service behind it — you are paying for a hop and a deployment and getting neither autonomy nor aggregation. In that case it is more honest to say “we have a server-side layer for sessions” than to call it a BFF: the word drags along expectations the construction does not meet.
What a BFF is not
It is not an API gateway. A gateway is an infrastructure component shared by all consumers: routing, rate limits, TLS termination, authentication at the edge. A BFF is application code owned by one team and aware of that team's screens. They are not in competition, and they often stand together: the gateway at the edge, the BFF behind it.
It is not “just a proxy.” A proxy that forwards requests one to one gives neither aggregation nor autonomy — the two things the pattern was invented for. It can be useful for the sake of tokens, but then it should be called by its own name rather than by the name of a 2015 pattern.
It is not GraphQL. GraphQL solves an adjacent problem — “let the client describe what it needs” — and solves it with a schema rather than with a dedicated backend per experience. Notice that in 2012 Netflix picked a third option: not a universal API and not a query language, but server-side adapters written by the UI teams. All three approaches answer the same question, but they distribute who writes the code and who is answerable for it differently.
How this looks in this application
A reader of this site never talks to the Go service directly: the browser knows only the Next.js routes under /api, and the session token lives in an HttpOnly cookie that scripts have no access to. The module the outgoing calls are made from is marked server-only — importing it from a client component breaks the build rather than making it as far as review.
This is a BFF in the second sense exactly — the one that came out of the IETF draft. There is almost no aggregation here: the routes mostly map one to one onto calls to the Go service. By Newman's criterion this is precisely the case where a BFF for the sake of aggregation would not be needed — and it is not here for that.
What is standard here and what is a team decision
| Statement | Status |
|---|---|
The token must not sit in localStorage | An OWASP requirement |
The session cookie is Secure and HttpOnly | A MUST of the IETF draft |
SameSite=Strict | A SHOULD of the IETF draft |
| Separate protection against CSRF is mandatory | A MUST of the IETF draft |
SameSite on its own does not protect against CSRF | Stated outright in rfc6265bis |
| “One experience, one BFF” | Newman's recommendation, not a standard |
| A web application needs a BFF only with noticeable aggregation | Newman's caveat, not a standard |
| Duplication between BFFs is tolerable | Newman's position, debatable |
Lax instead of Strict in this application | A team decision, justified above |
The split here is not a formality. The first five rows are things you will rightly be shown the specification for if you break them. The next three are the experience of particular people on particular systems, and they can be argued with. The last one is your own responsibility, and the only thing that makes it acceptable is the reason written down.
What to keep in mind
A pattern with two unconnected reasons to exist is more dangerous than most: it is easy to adopt it for one of them and decide you got both. A BFF put in place for tokens will not speed up a screen by itself. A BFF put in place for aggregation will not protect the token if you hand it to JavaScript anyway.
And neither of them will stop XSS — all it decides is what exactly the attacker takes with them when the tab closes.
Common misconceptions
“A BFF protects you from XSS.”
It changes the consequences of XSS, not the probability. The token really cannot be pulled out of the browser, but the script executes inside your origin and can send requests to the BFF as the user — the browser attaches the cookie. The IETF draft calls this proxying requests through the user's browser (section 5.1.4), and in the discussion of the BFF (6.1.4.1) it leaves that as the one scenario that still works, saying that nothing can prevent it except not letting foreign code execute.
“The BFF was invented for security.”
About the storage of tokens the primary sources from 2012–2015 (Netflix, Calçado, Newman) say nothing at all: they are about round-trip latency and about who has the right to change the code of an endpoint. Security does come up in Calçado, but a different kind — checking OAuth scopes and forging the keys of the “official application,” that is, who is allowed to call an endpoint. The argument about where the token is kept appeared a decade later, in the IETF draft on OAuth in the browser.
“We set SameSite, so CSRF is closed.”
rfc6265bis says in section 5.6.7.1 that Lax is a reasonable defense in depth against CSRF on unsafe methods, but not a complete defense against CSRF as a class. OWASP states the same thing as a rule: combine SameSite with a CSRF token rather than relying on it alone. The IETF draft makes this a separate MUST.
“A BFF is the same thing as an API gateway, only fashionable.”
A gateway is shared by all consumers and lives in the infrastructure: routing, rate limits, TLS. A BFF belongs to one product team and knows about that team's screens. They are not interchangeable, and they often stand together — the gateway at the edge, the BFF behind it.
“The pattern is a good one, so every web application needs a BFF.”
Newman calls this out separately (with a cautious “I suspect”): for an application with a web interface only, a BFF is justified only if there really is noticeable aggregation on the server. Without it you get an extra network hop and an extra deployment. A separate story is when the layer is needed for the sake of tokens: then it is the right decision, but for a different reason, and it is worth naming honestly.
Knowledge check
In an application the session sits in an HttpOnly cookie behind a BFF. XSS has fired on the page. What can the attacker do?
Sources & further reading
8 SOURCES
- Phil Calçado — The Back-end for Front-end Pattern (BFF)Source. September 18, 2015. A SoundCloud engineer describes how their monolith turned into a one-size-fits-all API and why a separate backend was stood up for the new iOS application. The text the name itself came from.https://philcalcado.com/2015/09/18/the_back_end_for_front_end_pattern_bff.html
- Sam Newman — Backends For FrontendsSource. The canonical description of the pattern (first published November 18, 2015). This is where the rule “one experience, one BFF” lives (Newman credits it to Stewart Gleadow), along with Pete Hodgson's observation about team boundaries and the warning about how attempts to merge BFFs back into a shared edge service end.https://samnewman.io/patterns/architectural/bff/
- Daniel Jacobson — Embracing the Differences: Inside the Netflix API RedesignSource. Netflix TechBlog, July 9, 2012. Dropping the one-size-fits-all REST API in favor of adapters written by the UI teams themselves. This is also the source of the figure “more than 800 device types”.https://netflixtechblog.com/embracing-the-differences-inside-the-netflix-api-redesign-15fd8b3dc49d
- Ben Christensen — Optimizing the Netflix APISource. Netflix TechBlog, January 15, 2013. The continuation of the same story: “2+ billion incoming requests a day” and the explanation of why it pays to spend the WAN latency once.https://netflixtechblog.com/optimizing-the-netflix-api-5c9ac715cf19
- draft-ietf-oauth-browser-based-apps — OAuth 2.0 for Browser-Based ApplicationsOfficial documentation. IETF draft, revision 27 (July 6, 2026), handed to the IESG and awaiting publication as a BCP. Section 6.1 describes the BFF as an architecture and states the requirements for cookies and CSRF; 5.1.4 dissects the attack the pattern does not close, and 6.1.4.1 honestly leaves it as the one scenario that still works.https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps-27
- draft-ietf-httpbis-rfc6265bis — Cookies: HTTP State Management MechanismOfficial documentation. The working replacement for RFC 6265. Section 5.6.7.1 defines the SameSite modes and says outright that Lax is not a complete defense against CSRF.https://httpwg.org/http-extensions/draft-ietf-httpbis-rfc6265bis.html
- OWASP — Cross-Site Request Forgery Prevention Cheat SheetOfficial documentation. The Synchronizer Token Pattern, Origin/Referer checking, and the section on when SameSite is enough on its own (spoiler: almost never).https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
- OWASP — Session Management Cheat SheetOfficial documentation. Requirements for the session identifier: at least 64 bits of entropy, the Secure and HttpOnly flags, and a flat prohibition on keeping tokens in localStorage and sessionStorage.https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html