Deep Engineering
Продвинутый·Опубликовано·30 МИН

Backend for Frontend: слой, который знает, кто спрашивает

Паттерн описали в 2015 году ради скорости и автономии команд. Через одиннадцать лет черновик IETF стал настойчиво рекомендовать его ради того, о чём его авторы не писали, — чтобы токен вообще не попадал в браузер.

Полное техническое изложение

TL;DR

  • BFF — это не слой между фронтендом и бэкендом «вообще». Это отдельный бэкенд на каждый пользовательский опыт, который принадлежит команде этого опыта.
  • Придумали его ради двух вещей: убрать круговые задержки и убрать очередь на изменение общего API. О том, где хранится токен, в исходных текстах нет ни слова.
  • Вторая жизнь паттерна началась в стандартах: черновик IETF по OAuth в браузере настойчиво рекомендует BFF, потому что при нём токен физически не попадает в JavaScript.
  • Чего BFF не делает: он не спасает от XSS. Скрипт, выполняющийся в вашем origin — в той же тройке «схема + домен + порт», что и страница, — всё так же прочитает данные пользователя — просто чужими руками. Это записано в самом черновике.

Зачем это знать?

Потому что «BFF» стало словом, которым называют что угодно между браузером и сервисами: прокси, шлюз, «наш api-роут в Next.js». Из-за этого разговор об архитектуре превращается в спор о названиях, а в код попадают решения, взятые из статьи про совсем другую задачу.

У паттерна есть авторы, даты публикаций и две разные причины существования, разнесённые на десятилетие. Первая — производительность и автономия команд: 2012 год у Netflix, 2015-й у SoundCloud. Вторая — хранение токенов, и она появилась не у архитекторов, а в тексте IETF. Понимать, какая из двух причин ваша, важнее, чем знать определение: от этого зависит, сколько BFF заводить, кому они принадлежат и что считать провалом.

Модель в голове

Представьте переводчика на переговорах. Универсальный API — это словарь: он честно переводит каждое слово, но чтобы понять фразу, вам придётся заглянуть в него шесть раз, и словарь не знает, что вы вообще-то ведёте переговоры о поставке. Переводчик знает: он слушает всю фразу, сам сверяется с чем нужно и отдаёт вам один готовый ответ — на вашем языке и в вашем контексте.

Ключевое в этой метафоре — чей переводчик. Он нанят вашей стороной и уходит вместе с ней. Это и есть главное свойство BFF: он принадлежит команде фронтенда, а не команде платформы, и меняется вместе с экраном, а не вместе с доменной моделью.

Откуда это на самом деле взялось

История начинается не с BFF, а с Netflix. В июле 2012 года Daniel Jacobson описал, почему их универсальный REST API перестал работать: компания поддерживала, по его словам, более 800 типов устройств, и один и тот же ответ подходил всем одинаково плохо. Вывод в статье сформулирован жёстко — такой API удобен тому, кто его отдаёт, а не тому, кто его потребляет.

Netflix решил это не «ещё одной точкой входа», а сменой владельца кода: UI-команды получили право писать серверные адаптеры под свои экраны. Через полгода Ben Christensen в продолжении описал масштаб — 2+ миллиарда входящих запросов в день — и главный выигрыш: собрать экран одним обращением, чтобы WAN-задержка платилась один раз.

Обратите внимание на то, чего в этих двух текстах нет. В них ни разу не обсуждается, где лежит токен. Это статьи про задержку и про то, кто пишет код.

Само название появилось в сентябре 2015 года: Phil Calçado, инженер SoundCloud, описал их путь — монолит, затем публичный API для всех клиентов сразу, затем упор в три стены. Третья стена самая недооценённая: каждую новую точку входа в API приходилось долго обсуждать, чтобы она не оказалась слишком узкой под конкретный экран. Под новое iOS-приложение команда завела отдельный бэкенд — «разные бэкенды для разных фронтендов».

Через два месяца Sam Newman оформил это в паттерн — и заодно сохранил для нас правило, из которого дальше считают, сколько BFF заводить. Сам он приписывает его Stewart Gleadow, а тот, в свою очередь, Phil Calçado и Mustafa Sezgin:

One experience, one BFF.

— Stewart Gleadow, цит. по Sam Newman, Backends For Frontends

Дословно: «один пользовательский опыт — один BFF». Не «один на фронтенд», не «один на команду», не «один на приложение». Единица деления — опыт: если iOS и Android почти одинаковы, им хватит одного BFF; если они разошлись — нужны два. Рядом Newman приводит наблюдение Pete Hodgson: на практике BFF лучше всего ложатся на границы команд, и именно структура команд должна определять их количество, — потому что делить надо то, что кто-то потом сопровождает.

Первая причина: круговые задержки складываются

Аргумент 2012 года выглядит очевидным ровно до тех пор, пока его не измеришь. Сеть в разработке кажется бесплатной, потому что на localhost она почти бесплатная.

Ниже — измерение. Реальный браузер против работающего приложения, задержка 150 мс на запрос, наложенная через Chrome DevTools Protocol; каждое число — медиана трёх прогонов. Пунктир и плитки — арифметика по вашим числам, а не измерение; ползунки нужны, чтобы подставить свои.

Здесь важен не сам факт «шесть запросов дольше одного», а то, во что превращается разница. Шесть последовательных обращений при наложенных 150 мс дали 972 мс по медиане трёх прогонов — больше, чем арифметические 6 × 150 на плитке: к наложенной задержке добавляется работа на обоих концах. Почти секунда, за которую на сервере ничего сложного не произошло. Тот же экран за одним обращением к BFF стоит одну задержку плюс работу на стороне сервера: обращение к соседнему сервису с той же машины обошлось в 0,71 мс по медиане двадцати замеров curl (поле serverLegLoopback).

Отсюда — важная оговорка, о которой в пересказах обычно забывают. Newman высказывает её осторожно — «I suspect», — но по сути прямо: для приложения с одним только веб-интерфейсом BFF оправдан, лишь если на сервере действительно есть что агрегировать. Если ваш экран — это один запрос к одному сервису, вы получите лишний сетевой прыжок и лишний деплой, а взамен ничего.

Вторая причина появилась десятилетием позже

Безопасность в текстах 2015 года упоминается — но совсем другая. У Calçado это первая из трёх проблем универсального API: приходилось всюду проверять OAuth-scope и мешать подделке ключей «официального приложения». Речь о том, кому можно вызывать точку входа, а не о том, где лежит токен. Аргумент про хранение токена пришёл позже и из другого места — из стандартизации.

Черновик IETF «OAuth 2.0 for Browser-Based Applications» (ревизия 27, июль 2026; передан в IESG и ждёт публикации как BCP) разбирает три архитектуры в порядке убывания безопасности, ставит BFF первой и настойчиво рекомендует её для бизнес-приложений, чувствительных приложений и всего, что работает с персональными данными. Её обязанности он определяет тремя пунктами: BFF выступает конфиденциальным OAuth-клиентом, хранит access- и refresh-токены внутри cookie-сессии и сам подставляет нужный токен в запросы к ресурсному серверу.

Черновик формулирует смысл конструкции в разделе 6.1: токены доступны только BFF, и странице извлекать нечего.

Это проверяемое утверждение, а не декларация. Ниже — измерение того, что достаёт скрипт, выполняющийся на странице, при трёх способах хранить сессию. Колонка «за BFF» — дословный вывод прогона против живого стенда (Next.js в продакшен-режиме перед Go-сервисом перед PostgreSQL); две другие сняты тем же способом на отдельной странице, куда синтетический токен положили в localStorage и в куку без HttpOnly.

Первые две пробы подтверждают то, что можно проверить со стороны страницы: ни localStorage, ни document.cookie не отдают скрипту ничего — хотя в банке браузера обе куки лежат. Заодно они объясняют, почему OWASP в Session Management Cheat Sheet прямо запрещает класть токены и идентификаторы сессий в localStorage или sessionStorage: там их видит любой скрипт вашего origin.

А вот третья проба — самое полезное, что есть в этой статье.

Чего BFF не делает

Переключите визуализацию выше на третью пробу. Скрипт, у которого нет токена, спокойно читает закладки пользователя: он просто делает fetch к своему же origin, а куку прикладывает браузер.

Это не дефект реализации. Черновик IETF называет эту атаку прямо — «проксирование запросов через браузер пользователя», раздел 5.1.4: вредоносный код выполняется в том же контексте, что и легитимное приложение, и потому может воспроизводить его поведение. В разборе BFF (раздел 6.1.4.1) этот сценарий остаётся единственным, который паттерн не закрывает, и там же сказано: помешать такому нельзя ничем, кроме недопущения самого выполнения чужого кода.

Практический вывод, который стоит проговорить вслух на любом ревью архитектуры:

BFF меняет последствия XSS, а не вероятность XSS. Украсть долгоживущий токен и уйти с ним в свою инфраструктуру атакующий больше не может. Действовать от имени пользователя, пока открыта вкладка, — по-прежнему может.

Разница реальная и большая: украденный refresh-токен работает после закрытия вкладки, из другой страны и без жертвы. Но продавать BFF как «защиту от XSS» — значит обещать не то, что он делает.

Что стандарт требует от куки

Раз сессия переехала в куку, требования к ней перестают быть делом вкуса. Черновик формулирует их в разделе 6.1.3.2 и 6.1.3.3 нормативной лексикой RFC 2119:

ТребованиеУровеньЧто это значит
SecureMUSTКука не уходит по открытому HTTP
HttpOnlyMUSTКуку не видно из document.cookie
SameSite=StrictSHOULDКука не прикладывается к кросс-сайтовым запросам вообще
Защита от CSRFMUSTОтдельный механизм, не заменяемый предыдущим пунктом

Последние две строки стоит развести, потому что их постоянно путают. SameSite — это поведение браузера, а не проверка на вашем сервере. rfc6265bis в разделе 5.6.7.1 говорит об этом без обиняков: режим Lax — разумная эшелонированная защита против CSRF, использующих небезопасные методы, но не полноценная защита от CSRF как класса атак. OWASP формулирует то же самое как правило: относиться к SameSite как к слою в глубину и сочетать его с CSRF-токеном, а не полагаться на него одного.

Отсюда — synchronizer token pattern: сервер выдаёт токен, привязанный к сессии, и на каждом изменяющем запросе сверяет пришедшее значение с сохранённым. Кросс-сайтовая форма может заставить браузер приложить куку, но не может прочитать ответ вашего origin — а значит, не узнает токен.

Честное отступление от стандарта: почему здесь Lax, а не Strict

Приложение, на котором сняты измерения выше, ставит SameSite=Lax. Черновик говорит SHOULD ... Strict. Это осознанное отклонение, и вот его причина.

Ссылки на подтверждение почты и сброс пароля приходят письмом. Переход по такой ссылке — кросс-сайтовая навигация верхнего уровня: письмо открыто в почтовом клиенте, а не на вашем сайте. При Strict браузер не приложит куку к этому переходу, и человек, только что подтвердивший адрес, попадёт на страницу, которая считает его гостем. При Lax кука прикладывается к навигациям верхнего уровня безопасными методами — то есть ровно к переходу по ссылке из письма, но не к кросс-сайтовому POST.

SHOULD в RFC 2119 и означает эту ситуацию: требование можно не выполнить, если вы поняли последствия. Последствие здесь — ослабленный первый эшелон защиты от CSRF, и он компенсирован вторым: synchronizer token со сравнением за постоянное время плюс проверка заголовка Origin. Проба «изменяющий запрос без CSRF-заголовка» в измерении выше показывает работу этого второго эшелона: запрос из того же origin, с валидной кукой, но без токена получает 403.

Что здесь важно методологически: отклонение от SHOULD перестаёт быть проблемой ровно тогда, когда оно записано вместе с причиной и с тем, чем его закрыли. Незаписанное отклонение — это просто баг, о котором пока никто не знает.

Сколько заводить BFF и чем за них платят

Правило «один опыт — один BFF» звучит просто и ломается на втором BFF. Newman не прячет проблему: между BFF накапливается дублирование. Чего он делать не советует — сливать их обратно в «общий агрегирующий edge-сервис»: такой сервис, по его наблюдению, раз за разом превращается в раздутый код с перемешанными задачами. То есть это тот самый универсальный API, от которого уходили, только с новым названием.

К самому дублированию он относится спокойно: I am fairly relaxed about duplicated code across services (я довольно спокойно отношусь к дублированию кода между сервисами). Это не запрет на вынос общего кода — Newman перечисляет и общую библиотеку, и отдельный сервис как варианты, — но предупреждает, что библиотека — главный источник связности, особенно если через неё генерируются клиенты к нижележащим сервисам. Практический смысл: дублирование между BFF — цена, которую платят осознанно, и выносить общее стоит тогда, когда оно уже устоялось, а не при первом совпадении.

Практическая рамка: BFF оправдан, когда у экрана есть свой темп изменений и своя команда. Если у вас один фронтенд, одна команда и один сервис за спиной — вы платите за прыжок и деплой, не получая ни автономии, ни агрегации. В этом случае честнее сказать «у нас серверный слой для сессий», чем называть его BFF: слово тянет за собой ожидания, которых конструкция не оправдывает.

Чем BFF не является

Это не API Gateway. Шлюз — инфраструктурный компонент, общий для всех потребителей: маршрутизация, лимиты, терминация TLS, аутентификация на входе. BFF — прикладной код, принадлежащий одной команде и знающий про её экраны. Они не конкурируют и часто стоят вместе: шлюз на входе, BFF за ним.

Это не «просто прокси». Прокси, который один в один пересылает запросы, не даёт ни агрегации, ни автономии — двух вещей, ради которых паттерн придумали. Он может быть полезен ради токенов, но тогда это следует называть своим именем, а не паттерном 2015 года.

Это не GraphQL. GraphQL решает соседнюю задачу — «клиент сам описывает, что ему нужно», — и решает её схемой, а не выделенным бэкендом на каждый опыт. Заметьте, что Netflix в 2012 году выбрал третий вариант: не универсальный API и не язык запросов, а серверные адаптеры, которые пишут UI-команды. Все три подхода отвечают на один вопрос, но по-разному распределяют, кто пишет код и кто за него отвечает.

Своя версия и общая трасса

У BFF, привязанного к одному клиенту, две эксплуатационные особенности, которые обычно замечают поздно.

Первая — версионирование. Microsoft формулирует её как следствие самого паттерна:

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.

перевод

Оценивайте оптимальное число сервисов исходя из связанных с ними затрат. Сопровождение и развёртывание большего числа сервисов означает возросшие эксплуатационные накладные расходы. У каждого отдельного сервиса свой жизненный цикл, свои требования к развёртыванию и сопровождению и свои потребности в безопасности.

— Microsoft Azure Architecture Center — Backends for Frontends

«Свой жизненный цикл» у BFF означает цикл его клиента. Веб-фронтенд обновляется одновременно с BFF — старую версию можно снять в тот же день. Мобильное приложение обновляется у пользователя, и не у всех: старая версия ходит в BFF месяцами. Это разные режимы совместимости у сервисов, которые выглядят одинаково.

Придумывать формат отказа от старого не нужно, для него есть нормативный документ:

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.

перевод

Заголовок ответа Deprecation используется, чтобы сообщить потребителям ресурса (заданного через URI), что ресурс будет объявлен устаревшим или уже объявлен.

— RFC 9745 — The Deprecation HTTP Response Header Field

Заголовок несёт дату, а не флаг: Deprecation: @1688169599. Рядом с ним стоит Sunset — дата отключения, — и между ними RFC устанавливает порядок: The timestamp given in the Sunset HTTP header field MUST NOT be earlier than the one given in the Deprecation header field (Отметка времени в заголовке Sunset НЕ ДОЛЖНА быть раньше, чем отметка в заголовке Deprecation). В примере самого документа между объявлением и отключением проходит год — и это хороший ориентир для мобильного клиента, для которого «мы выкатили» не значит «у всех обновилось».

Вторая — наблюдаемость, и тут у BFF особая роль. Один клиентский запрос разветвляется в несколько внутренних, то есть BFF — то самое место, где трасса может порваться.

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.

перевод

Распределённая трассировка — методика, реализуемая инструментами трассировки, чтобы прослеживать, анализировать и отлаживать транзакцию через несколько программных компонентов. Обычно распределённая трасса проходит более чем через один компонент, что требует её однозначной идентифицируемости во всех участвующих системах.

— W3C Trace Context

Требование к посреднику стандарт разделяет на два уровня, и минимальный — обязателен:

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.

перевод

Как минимум они ОБЯЗАНЫ распространять заголовки traceparent и tracestate и гарантировать, что трассы не рвутся. Это поведение также называют пересылкой трассы.

— Там же

То есть BFF, не пересылающий traceparent дальше, рвёт трассу ровно там, где она нужнее всего: на разветвлении. Формат заголовка фиксирован и разбирается тривиально — traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01, четыре поля: версия, идентификатор трассы, идентификатор родителя, флаги.

Отдельная оговорка, которая противоречит соблазну «сложим всю наблюдаемость в 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.

перевод

Сервис BFF должен обрабатывать только специфичную для клиента логику, относящуюся к конкретному пользовательскому опыту. Сквозные возможности, такие как мониторинг и авторизация, следует выносить наружу, чтобы сохранять эффективность.

— Microsoft Azure Architecture Center — Backends for Frontends

Пересылать контекст — обязанность BFF. Быть местом, где живёт мониторинг всей системы, — нет: тогда каждый новый клиентский опыт потребует копии этой машинерии.

Federation: то же лекарство, разрез по другой оси

Выше сказано, что GraphQL решает соседнюю задачу схемой, а не выделенным бэкендом. У этого утверждения есть продолжение, о котором стоит знать при выборе: у GraphQL есть свой ответ на ту же боль, что породила BFF, — очередь команд на изменение общего API.

Начинается он с прямо противоположной посылки:

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.

перевод

Чтобы получить максимум от GraphQL, ваша организация должна выставлять единый граф, дающий унифицированный интерфейс для запросов к любой комбинации ваших источников данных.

— Apollo — Introduction to Apollo Federation

Единый граф — это ровно тот универсальный API на всех потребителей, от которого уходили Netflix и SoundCloud. Federation не отменяет его, а решает вторую половину задачи — автономию команд — внутри него:

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.

перевод

Apollo Federation поощряет принцип проектирования, называемый разделением ответственности. Он позволяет разным командам работать над разными продуктами и возможностями внутри единого графа, не мешая друг другу.

— Там же

Вот и весь критерий выбора, и он в одну строку: federation делит по владельцу домена, BFF — по потребителю. Обе конструкции убирают очередь на изменение общего API. Federation оставляет один интерфейс на всех клиентов, а автономию даёт командам, владеющим данными. BFF заводит интерфейс на каждого клиента, а автономию даёт командам, владеющим опытом.

Отсюда и практическое правило. Если больно оттого, что у клиентов разные потребности к форме ответа — это BFF. Если больно оттого, что несколько команд пишут в одну схему — это federation. Если больно и от того, и от другого, они совмещаются: BFF поверх суперграфа.

Цену federation надо включать в сравнение честно, потому что она не в шлюзе, а в каждом нижележащем сервисе:

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.

перевод

Чтобы GraphQL-сервис работал как подграф Apollo Federation 2, он обязан сделать всё следующее: автоматически расширить свою схему всеми определениями, перечисленными в „Дополнениях к схеме подграфа“; корректно разрешать расширенное интроспекционное поле Query._service; предоставить разработчикам подграфа механизм разрешения полей сущностей через поле Query._entities; применить директиву @link к типу schema.

— Apollo — Federation Subgraph Specification

Каждый сервис перестаёт быть просто HTTP-сервисом и обязан реализовать спецификацию. BFF такого не требует ни от кого: нижележащие сервисы о нём не знают.

И симметричная честность в другую сторону — Microsoft прямо допускает, что при GraphQL отдельный слой может быть не нужен:

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.

перевод

При GraphQL механизм запросов устраняет необходимость в отдельном слое BFF, поскольку позволяет клиентам запрашивать нужные им данные, не опираясь на заранее заданные точки входа. […] Оцените, нужен ли вам этот паттерн вообще. Например, если ваша организация использует GraphQL с резолверами под конкретный фронтенд, BFF-сервисы могут не давать вашим приложениям никакой добавленной ценности.

— Microsoft Azure Architecture Center — Backends for Frontends

Оговорка «ради сессии» из следующего раздела к этому не относится: токен в HttpOnly-куке нужен независимо от того, REST у вас или GraphQL.

Как это выглядит в этом приложении

Читатель этого сайта не ходит в Go-сервис напрямую: браузер знает только маршруты Next.js под /api, а токен сессии живёт в HttpOnly-куке, к которой у скриптов нет доступа. Модуль, из которого делаются исходящие вызовы, помечен server-only — импорт его из клиентского компонента ломает сборку, а не проходит ревью.

Это BFF во втором смысле — ради сессии, а не ради агрегации. Но не буквально по черновику: OAuth здесь нет, а вместо access- и refresh-токенов — непрозрачный идентификатор сессии, который хранится на сервере и проверяется по каждому запросу (ADR-005). По собственному критерию выше этот слой ближе к «серверному слою для сессий», чем к BFF образца 2015 года; слово оставлено потому, что его так называет черновик IETF, а не потому, что здесь есть агрегация. Агрегации здесь почти нет: маршруты в основном один к одному соответствуют вызовам Go-сервиса. По критерию Newman это как раз тот случай, когда BFF ради агрегации был бы не нужен, — и он здесь стоит не ради неё.

Что здесь стандарт, а что — решение команды

УтверждениеСтатус
Токен не должен лежать в localStorageТребование OWASP
Кука сессии — Secure и HttpOnlyMUST черновика IETF
SameSite=StrictSHOULD черновика IETF
Отдельная защита от CSRF обязательнаMUST черновика IETF
SameSite в одиночку от CSRF не защищаетПрямо сказано в rfc6265bis
«Один опыт — один BFF»Рекомендация Newman, не стандарт
BFF нужен веб-приложению только при заметной агрегацииОговорка Newman, не стандарт
Дублирование между BFF терпимоПозиция Newman, спорная
Lax вместо Strict в этом приложенииРешение команды, обосновано выше

Разделение здесь не формальность. Первые пять строк — то, за нарушение чего вам справедливо укажут на спецификацию. Следующие три — опыт конкретных людей, полученный на конкретных системах, и их можно оспаривать. Последняя — ваша собственная ответственность, и единственное, что делает её приемлемой, — записанная причина.

Что стоит помнить

Паттерн, у которого две несвязанные причины существования, опаснее обычного: легко внедрить его ради одной и решить, что получил обе. BFF, поставленный ради токенов, не ускорит экран сам по себе. BFF, поставленный ради агрегации, не защитит токен, если вы всё равно отдаёте его в JavaScript.

И ни один из них не остановит XSS — он лишь решает, что именно атакующий унесёт с собой, когда вкладка закроется.

Чем измерено

Числа этой статьи получены этими скриптами. Каждый открывается прямо отсюда — вместе с записью прогона: на чём считали, что получилось и с каким разбросом.

Унести в работу

  • Не продавать BFF как защиту от XSS: он решает, что злоумышленник унесёт с собой, а не сможет ли он навредить прямо сейчас.
  • Не заводить BFF «потому что паттерн хороший»: первая его причина — собрать экран одним обращением вместо шести, и если этой задачи нет, нет и повода.
  • Заводить по BFF на экран, а не один на всех: тем он и отличается от API Gateway.
  • Не считать SameSite закрытым CSRF: куку браузер прикладывает сам, и защиту от подделки запроса надо ставить отдельно.

Частые заблуждения

Утверждение

«BFF защищает от XSS».

На самом деле

Он меняет последствия XSS, а не вероятность. Токен действительно не достать из браузера, но скрипт выполняется внутри вашего origin и может слать запросы к BFF от имени пользователя — куку приложит браузер. Черновик IETF называет это проксированием запросов через браузер пользователя (раздел 5.1.4), а в разборе BFF (6.1.4.1) оставляет её единственным работающим сценарием и говорит, что помешать этому нельзя ничем, кроме недопущения выполнения чужого кода.

Утверждение

«BFF придумали ради безопасности».

На самом деле

О хранении токенов в первоисточниках 2012–2015 годов (Netflix, Calçado, Newman) нет ни слова: там речь о круговых задержках и о том, кто имеет право менять код точки входа. Безопасность у Calçado упоминается, но другая — проверки OAuth-scope и подделка ключей «официального приложения», то есть кому можно вызывать точку входа. Аргумент про то, где лежит токен, появился десятилетием позже — в черновике IETF по OAuth в браузере.

Утверждение

«Поставили SameSite — CSRF закрыт».

На самом деле

rfc6265bis в разделе 5.6.7.1 говорит, что Lax — разумная эшелонированная защита против CSRF на небезопасных методах, но не полноценная защита от CSRF как класса. OWASP формулирует то же самое как правило: сочетать SameSite с CSRF-токеном, а не полагаться на него одного. Черновик IETF выносит это в отдельное MUST.

Утверждение

«BFF — это то же самое, что API Gateway, только модное».

На самом деле

Шлюз общий для всех потребителей и живёт в инфраструктуре: маршрутизация, лимиты, TLS. BFF принадлежит одной продуктовой команде и знает про её экраны. Они не взаимозаменяемы и часто стоят вместе — шлюз на входе, BFF за ним.

Утверждение

«Раз паттерн хороший, BFF нужен любому веб-приложению».

На самом деле

Newman оговаривает это отдельно (осторожным «I suspect»): для приложения с одним только веб-интерфейсом BFF оправдан, лишь если на сервере действительно есть заметная агрегация. Без неё вы получаете лишний сетевой прыжок и лишний деплой. Отдельная история — если слой нужен ради токенов: тогда это верное решение, но по другой причине, и называть его стоит честно.

Проверка знаний

Вопрос 1 из 5

В приложении сессия лежит в HttpOnly-куке за BFF. На странице сработала XSS. Что атакующий может сделать?

Источники и что читать дальше

12 ИСТОЧНИКОВ

  1. Phil Calçado — The Back-end for Front-end Pattern (BFF)Источник. 18 сентября 2015. Инженер SoundCloud описывает, как их монолит превратился в one-size-fits-all API и почему под новое iOS-приложение завели отдельный бэкенд. Текст, из которого пошло само название.https://philcalcado.com/2015/09/18/the_back_end_for_front_end_pattern_bff.html
  2. Sam Newman — Backends For FrontendsИсточник. Каноническое описание паттерна (первая публикация — 18 ноября 2015). Здесь собраны правило «one experience, one BFF» (один интерфейс — один BFF) (Newman приводит его от Stewart Gleadow), наблюдение Pete Hodgson про границы команд и предупреждение о том, чем кончаются попытки слить BFF обратно в общий edge-сервис.https://samnewman.io/patterns/architectural/bff/
  3. Daniel Jacobson — Embracing the Differences: Inside the Netflix API RedesignИсточник. Netflix TechBlog, 9 июля 2012. Отказ от универсального REST API в пользу адаптеров, которые пишут сами UI-команды. Здесь же цифра «более 800 типов устройств».https://netflixtechblog.com/embracing-the-differences-inside-the-netflix-api-redesign-15fd8b3dc49d
  4. Ben Christensen — Optimizing the Netflix APIИсточник. Netflix TechBlog, 15 января 2013. Продолжение той же истории: «2+ миллиарда входящих запросов в день» и объяснение, почему WAN-задержку выгодно платить один раз.https://netflixtechblog.com/optimizing-the-netflix-api-5c9ac715cf19
  5. draft-ietf-oauth-browser-based-apps — OAuth 2.0 for Browser-Based ApplicationsОфициальная документация. Черновик IETF, ревизия 27 (6 июля 2026), передан в IESG и ждёт публикации как BCP. Раздел 6.1 описывает BFF как архитектуру и формулирует требования к кукам и CSRF; 5.1.4 разбирает атаку, которую паттерн не закрывает, а 6.1.4.1 честно оставляет её единственным работающим сценарием.https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps-27
  6. draft-ietf-httpbis-rfc6265bis — Cookies: HTTP State Management MechanismОфициальная документация. Рабочая версия замены RFC 6265. Раздел 5.6.7.1 определяет режимы SameSite и прямо говорит, что Lax — не полноценная защита от CSRF.https://httpwg.org/http-extensions/draft-ietf-httpbis-rfc6265bis.html
  7. OWASP — Cross-Site Request Forgery Prevention Cheat SheetОфициальная документация. Synchronizer Token Pattern, проверка Origin/Referer и раздел о том, когда SameSite достаточно самой по себе (спойлер: почти никогда).https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
  8. OWASP — Session Management Cheat SheetОфициальная документация. Требования к идентификатору сессии: минимум 64 бита энтропии, флаги Secure и HttpOnly, прямой запрет хранить токены в localStorage и sessionStorage.https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
  9. RFC 9745 — The Deprecation HTTP Response Header FieldОфициальная документация. Нормативный способ объявить версию устаревшей: «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» (Заголовок ответа Deprecation используется, чтобы сообщить потребителям ресурса, заданного через URI, что ресурс будет объявлен устаревшим или уже объявлен). Значение — дата, а не флаг. Порядок относительно Sunset задан нормативно: «The timestamp given in the Sunset HTTP header field MUST NOT be earlier than the one given in the Deprecation header field» (Отметка времени в заголовке Sunset НЕ ДОЛЖНА быть раньше, чем отметка в заголовке Deprecation). В примере самого документа между этими датами проходит год.https://www.rfc-editor.org/rfc/rfc9745.html
  10. W3C Trace Context (Recommendation)Официальная документация. Минимальное требование к посреднику, из-за которого BFF обязан пересылать заголовки: «At a minimum they MUST propagate the traceparent and tracestate headers and guarantee traces are not broken» (Как минимум они ОБЯЗАНЫ распространять заголовки traceparent и tracestate и гарантировать, что трассы не рвутся). Формат traceparent фиксирован: версия, идентификатор трассы, идентификатор родителя, флаги.https://www.w3.org/TR/trace-context/
  11. Microsoft Azure Architecture Center — Backends for FrontendsОфициальная документация. Откуда берётся требование к версионированию: «Each individual service has its own life cycle, deployment and maintenance requirements, and security needs» (У каждого отдельного сервиса свой жизненный цикл, свои требования к развёртыванию и сопровождению и свои потребности в безопасности). И граница ответственности BFF: «Cross-cutting features, such as monitoring and authorization, should be abstracted to maintain efficiency» (Сквозные возможности, такие как мониторинг и авторизация, следует выносить наружу, чтобы сохранять эффективность). Оттуда же признание, что при GraphQL слой BFF может быть не нужен вовсе.https://learn.microsoft.com/en-us/azure/architecture/patterns/backends-for-frontends
  12. Apollo — Introduction to Federation и Subgraph SpecificationОфициальная документация. Посылка, противоположная BFF: «your organization should expose a single graph that provides a unified interface» (ваша организация должна выставлять единый граф, дающий унифицированный интерфейс), и то, ради чего federation придумана: «This enables different teams to work on different products and features within a single graph, without interfering with each other» (Он позволяет разным командам работать над разными продуктами и возможностями внутри единого графа, не мешая друг другу). Цена — в спецификации подграфа: каждый нижележащий сервис обязан расширить схему, разрешать Query._service и Query._entities и применить директиву @link.https://www.apollographql.com/docs/federation/