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 never reaches anywhere JavaScript can read it — the browser keeps it in a cookie and attaches it itself.
- 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.
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 run, a call to a neighboring service — from the machine itself, not from the browser — 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.
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 exact opposite in plain words: it is a useful extra layer, but not a complete defense. So the server additionally issues a CSRF token bound to the session: the page fetches it in a separate request to its own server and attaches it to every action that changes something. A form on someone else's site cannot read your server's response, so it never learns the token. Against a foreign script on your own page this does nothing: it will fetch the token exactly as the page does.
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 six 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.
What matters here is not the fact that “six requests take longer than one” but what the difference turns into. Six sequential calls at an injected 150 ms came to 972 ms at the median of three runs — more than the arithmetic 6 × 150 on the tile: on top of the injected latency there is work at both ends. 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 on the server side: a call to a neighboring service from the same machine came to 0.71 ms at the median of twenty curl samples (field serverLegLoopback).
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 draft states the point of the whole construction in section 6.1: the tokens are available to the BFF only, and there is nothing for the page 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.
The first two probes confirm what can be checked from the page's side: neither localStorage nor document.cookie hands the script anything — though both cookies are sitting in the browser's jar. 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 it says in the same place that nothing can prevent this 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.
Its own version, and a shared trace
A BFF tied to one client has two operational properties that are usually noticed late.
The first is versioning. Microsoft states it as a consequence of the pattern itself:
Evaluate your optimal number of services depending on the associated costs.
Maintaining and deploying more services means increased operational overhead.
Each individual service has its own life cycle, deployment and maintenance
requirements, and security needs.
"Its own life cycle" for a BFF means its client's life cycle. A web frontend ships together with its BFF — the old version can be removed the same day. A mobile app updates on the user's device, and not on everyone's: the old version keeps calling the BFF for months. These are different compatibility regimes for services that look alike.
The format for retiring the old need not be invented; there is a normative document for it:
The Deprecation HTTP response header field is used to signal to consumers of a
resource (identified by a URI) that the resource will be or has been deprecated.
The header carries a date, not a flag: Deprecation: @1688169599. Beside it
stands Sunset — the date of removal — and the RFC fixes their order:
The timestamp given in the Sunset HTTP header field MUST NOT be earlier than the one given in the Deprecation header field
.
In the document's own example a year passes between announcement and removal —
a reasonable benchmark for a mobile client, where "we shipped" does not mean
"everyone updated".
The second is observability, and here a BFF has a particular role. One client request fans out into several internal ones, which makes the BFF exactly the place where a trace can break.
Distributed tracing is a methodology implemented by tracing tools to follow,
analyze and debug a transaction across multiple software components. Typically, a
distributed trace traverses more than one component which requires it to be
uniquely identifiable across all participating systems.
The standard splits what it requires of an intermediary into two levels, and the minimum is mandatory:
At a minimum they MUST propagate the traceparent and tracestate headers and
guarantee traces are not broken. This behavior is also referred to as forwarding
a trace.
So a BFF that does not forward traceparent breaks the trace exactly where it is
needed most: at the fan-out. The header's format is fixed and trivially parsed —
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01, four
fields: version, trace id, parent id, flags.
One caveat runs against the temptation to put all observability into the BFF:
The BFF service should only handle client-specific logic related to a specific
user experience. Cross-cutting features, such as monitoring and authorization,
should be abstracted to maintain efficiency.
Forwarding context is the BFF's duty. Being the home of the whole system's monitoring is not: then every new client experience would need a copy of that machinery.
Federation: the same medicine, cut along a different axis
Above it was said that GraphQL solves the neighbouring problem with a schema rather than a dedicated backend. That claim has a sequel worth knowing when choosing: GraphQL has its own answer to the very pain that produced the BFF — the queue of teams waiting to change a shared API.
It starts from precisely the opposite premise:
To get the most out of GraphQL, your organization should expose a single graph
that provides a unified interface for querying any combination of your backing
data sources.
A single graph is exactly the one-size-fits-all API that Netflix and SoundCloud walked away from. Federation does not abolish it; it solves the other half of the problem — team autonomy — inside it:
Apollo Federation encourages a design principle called separation of concerns.
This enables different teams to work on different products and features within a
single graph, without interfering with each other.
That is the whole selection criterion, in one line: federation divides by data owner, a BFF divides by consumer. Both remove the queue to change a shared API. Federation keeps one interface for all clients and gives autonomy to the teams that own the data. A BFF creates an interface per client and gives autonomy to the teams that own the experience.
Hence the practical rule. If the pain is that clients want differently shaped responses — that is a BFF. If the pain is that several teams write into one schema — that is federation. If both hurt, they compose: a BFF over a supergraph.
Federation's price belongs in the comparison honestly, because it is not in the gateway but in every service underneath:
For a GraphQL service to operate as an Apollo Federation 2 subgraph, it must do
all of the following: Automatically extend its schema with all definitions listed
in Subgraph schema additions; Correctly resolve the Query._service enhanced
introspection field; Provide a mechanism for subgraph developers to resolve
entity fields via the Query._entities field; Apply the @link directive to the
schema type.
Every service stops being merely an HTTP service and has to implement a specification. A BFF requires that of nobody: the services underneath do not know it exists.
And the symmetric honesty in the other direction — Microsoft says outright that with GraphQL a separate layer may be unnecessary:
With GraphQL, the querying mechanism eliminates the need for a separate BFF layer
because it allows clients to request the data that they need without relying on
predefined endpoints. […] Evaluate whether you need this pattern. For example, if
your organization uses GraphQL with frontend specific resolvers, BFF services
might not add value to your applications.
The "for the sake of the session" caveat in the next section is unaffected: a
token in an HttpOnly cookie is needed whether you run REST or GraphQL.
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.
What measured this
The numbers in this article come from these scripts. Each one opens from here, together with the record of the run: what it was measured on, what came out, and with what spread.
This is neither a retelling nor a separate text: everything below is taken from the article itself — its own summary, the section headings, the “actually” column and the version table. Which is why these theses cannot drift from the article.
The gist
- 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.
In fact
- 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.
- 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.
- rfc6265bis says in section 5.6.7.1 that
Laxis 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: combineSameSitewith a CSRF token rather than relying on it alone. The IETF draft makes this a separateMUST. - 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.
- 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.
What is covered
- Why know this?
- The mental model
- Where this actually came from
- The first reason: round trips add up
- The second reason arrived a decade later
- What the BFF does not do
- What the standard requires of the cookie
- An honest deviation: why `Lax` here and not `Strict`
- How many BFFs to run, and what they cost
- What a BFF is not
- Its own version, and a shared trace
- Federation: the same medicine, cut along a different axis
- How this looks in this application
- What is standard here and what is a team decision
- What to keep in mind
- What measured this
Take to work
- Do not sell a BFF as protection from XSS: it decides what an attacker walks away with, not whether they can do damage right now.
- Do not adopt a BFF because the pattern is good: its first reason is assembling a screen in one round trip instead of six, and without that task there is no reason.
- Run one BFF per screen rather than one for everyone: that is what separates it from an API gateway.
- Do not treat
SameSiteas CSRF closed: the browser attaches the cookie by itself, and request-forgery protection is a separate piece.
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
12 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
- RFC 9745 — The Deprecation HTTP Response Header FieldOfficial documentation. The normative way to announce a version as deprecated: "The Deprecation HTTP response header field is used to signal to consumers of a resource (identified by a URI) that the resource will be or has been deprecated." Its value is a date, not a flag. Its order relative to
Sunsetis normative: "The timestamp given in the Sunset HTTP header field MUST NOT be earlier than the one given in the Deprecation header field." In the document's own example a year passes between the two dates.https://www.rfc-editor.org/rfc/rfc9745.html - W3C Trace Context (Recommendation)Official documentation. The minimum requirement on an intermediary, which is why a BFF must forward the headers: "At a minimum they MUST propagate the traceparent and tracestate headers and guarantee traces are not broken." The
traceparentformat is fixed: version, trace id, parent id, flags.https://www.w3.org/TR/trace-context/ - Microsoft Azure Architecture Center — Backends for FrontendsOfficial documentation. Where the versioning requirement comes from: "Each individual service has its own life cycle, deployment and maintenance requirements, and security needs." And the boundary of a BFF's responsibility: "Cross-cutting features, such as monitoring and authorization, should be abstracted to maintain efficiency." The same page concedes that with GraphQL a BFF layer may not be needed at all.https://learn.microsoft.com/en-us/azure/architecture/patterns/backends-for-frontends
- Apollo — Introduction to Federation and the Subgraph SpecificationOfficial documentation. The premise opposite to a BFF's: "your organization should expose a single graph that provides a unified interface", and what federation exists for: "This enables different teams to work on different products and features within a single graph, without interfering with each other." The price is in the subgraph specification: every service underneath must extend its schema, resolve
Query._serviceandQuery._entities, and apply the@linkdirective.https://www.apollographql.com/docs/federation/