Сценарии авторизации BFF
Статус плана
Проект: проверить после внедрения. Ниже описано целевое поведение сессии; пометки у сценариев сохраняют эту границу проверки.
Семь сценариев показывают вход, запросы и завершение сессии по шагам. Правила — cookie сессии, обработка вызова и CSRF. Общая картина — обзор трека.
Сценарии
Участники: F — код фронта, B — браузер, P — BFF, A — Go API.
Вход
Фронт шлёт логин с заголовком X-CSRF: 1. BFF проверяет заголовок и Origin, вызывает Go и получает токены в JSON. Токены остаются в BFF: браузеру уходят User и новая cookie, а три старые cookie стираются.
Проект: проверить после внедрения.
sequenceDiagram
participant F as Фронт
participant B as Браузер
participant P as BFF
participant A as Go API
F->>B: POST /api/trpc/auth.login, X-CSRF: 1
B->>P: POST /api/trpc/auth.login, Origin свой
Note over P: заголовок X-CSRF есть, Origin равен APP_ORIGIN
P->>A: POST /api/v1/auth/login
A-->>P: 200 { user, tokens }
P-->>B: 200 { result: { data: User } }, Set-Cookie __Host-Http-session
Note over P,B: тот же ответ стирает access_token, refresh_token и __Host-csrf
B-->>F: 200 UserЗагрузка схемы…
Запрос данных
BFF расшифровывает cookie и подставляет access токен как Authorization: Bearer. Фронт про токены ничего не знает.
Проект: проверить после внедрения.
sequenceDiagram
participant F as Фронт
participant B as Браузер
participant P as BFF
participant A as Go API
F->>B: GET /api/trpc/notebooks.list, X-CSRF: 1
B->>P: GET /api/trpc/notebooks.list, Cookie __Host-Http-session
Note over P: расшифровывает cookie, access ещё живой
P->>A: GET /api/v1/notebooks, Authorization: Bearer
A-->>P: 200
P-->>B: 200
B-->>F: 200Загрузка схемы…
Access истёк
Если до accessExp меньше 30 с, BFF обновляет токены сам, а потом выполняет исходный запрос. Фронт делает один запрос и не замечает refresh; новая cookie приходит с обычным ответом.
Проект: проверить после внедрения.
sequenceDiagram
participant F as Фронт
participant B as Браузер
participant P as BFF
participant A as Go API
F->>B: GET /api/trpc/notebooks.list, X-CSRF: 1
B->>P: GET /api/trpc/notebooks.list, Cookie __Host-Http-session
Note over P: до accessExp меньше 30 с
P->>A: POST /api/v1/auth/refresh, refresh_token
A-->>P: 200 TokenPair
P->>A: GET /api/v1/notebooks, Authorization: Bearer новый access
A-->>P: 200
P-->>B: 200, Set-Cookie __Host-Http-session новая
B-->>F: 200Загрузка схемы…
Параллельные RPC
users.me и notebooks.list отправлены из одной вкладки разными HTTP-запросами со старой cookie, у которой access почти истёк. Go получает один вызов refresh, а оба RPC используют новую пару. После refresh вызовы данных в Go независимы: они могут выполняться параллельно и завершаться в любом порядке.
Проект: проверить после внедрения.
sequenceDiagram
participant F as Фронт
participant B as Браузер
participant P as BFF
participant A as Go API
F->>B: GET users.me и notebooks.list
B->>P: GET /api/trpc/users.me, старая cookie
B->>P: GET /api/trpc/notebooks.list, старая cookie
P->>A: POST /api/v1/auth/refresh, refresh_token
Note over P: оба RPC ждут одну Promise в inflight
A-->>P: 200 TokenPair
par users.me
P->>A: GET /api/v1/users/me, Bearer новый access
A-->>P: 200
and notebooks.list
P->>A: GET /api/v1/notebooks, Bearer новый access
A-->>P: 200
end
P-->>B: users.me, Set-Cookie новая
P-->>B: notebooks.list, Set-Cookie с той же парой
Note over P: успешный результат хранится ещё 10 сЗагрузка схемы…
Выход
Клиент сначала дожидается остальных защищённых HTTP-запросов или подтверждает их отмену, а новые не отправляет. Иначе ответ старого RPC может прийти после успешного выхода и восстановить cookie. Это правило действует для разных RPC, даже если они не попали в один батч.
После проверки CSRF процедура auth.logout (при истёкшем access — после refresh) вызывает Go с Bearer и refresh_token в теле, стирает cookie и отвечает успехом. Даже если Go вернул 401 (токен уже недействителен), клиент получает успех: сессии больше нет в любом случае. Без cookie сессии Go не вызывается, а ответ тот же; три старые cookie стираются всегда.
Проект: проверить после внедрения.
sequenceDiagram
participant F as Фронт
participant B as Браузер
participant P as BFF
participant A as Go API
F->>B: GET /api/trpc/notebooks.list
B->>P: GET /api/trpc/notebooks.list
F->>B: пользователь нажал «Выход»
Note over F,B: новые защищённые RPC не отправляются
P->>A: GET /api/v1/notebooks
A-->>P: 200
P-->>B: ответ старого RPC, возможный Set-Cookie
B-->>F: прежний запрос завершился
F->>B: POST /api/trpc/auth.logout, X-CSRF: 1
B->>P: POST /api/trpc/auth.logout, Cookie __Host-Http-session
P->>A: POST /api/v1/auth/logout, Authorization: Bearer, { refresh_token }
A-->>P: 204
P-->>B: 200 { result: {} }, Set-Cookie __Host-Http-session Max-Age=0
Note over P: успех и когда Go ответил 401
Note over P,B: тот же ответ стирает access_token, refresh_token и __Host-csrf
B-->>F: успехЗагрузка схемы…
Сессия кончилась
Refresh-токен отозван или не принят Go. Go отвечает 401, BFF стирает cookie и отвечает UNAUTHORIZED; фронт показывает форму входа. Если refresh просто истёк, браузер уже удалил cookie (Max-Age = refresh_expires_in), и authedProcedure отвечает UNAUTHORIZED, не обращаясь в Go.
Проект: проверить после внедрения.
sequenceDiagram
participant F as Фронт
participant B as Браузер
participant P as BFF
participant A as Go API
F->>B: GET /api/trpc/users.me, X-CSRF: 1
B->>P: GET /api/trpc/users.me, Cookie __Host-Http-session
Note over P: до accessExp меньше 30 с
P->>A: POST /api/v1/auth/refresh, refresh_token
A-->>P: 401
Note over P: refresh отозван или не принят, сессия кончилась
P-->>B: 401 UNAUTHORIZED, Set-Cookie Max-Age=0
B-->>F: 401
Note over F: гость - показать форму входаЗагрузка схемы…
Первый вход после переезда
У пользователя в браузере старые cookie access_token, refresh_token и __Host-csrf, а новой __Host-Http-session нет. HTML содержит user: null без вызова Go; клиент показывает форму входа. Пользователь входит заново, и ответ входа ставит новую cookie и стирает три старые.
Проект: проверить после внедрения.
sequenceDiagram
participant F as Фронт
participant B as Браузер
participant P as BFF
B->>P: GET HTML через Caddy, старые cookie, без __Host-Http-session
Note over P: Go не вызывается
P-->>B: 200 HTML с user null
B-->>F: bootstrap user null
Note over F: гость - показать форму входа
F->>B: POST /api/trpc/auth.login, X-CSRF: 1
B->>P: POST /api/trpc/auth.login
P-->>B: 200 { result: { data: User } }, Set-Cookie __Host-Http-session
Note over P,B: тот же ответ стирает access_token, refresh_token и __Host-csrfЗагрузка схемы…