Bollywood HungamaGaiety-Galaxy made stars feel like SUPERSTARS; where are they now? Bollywood's RADIO SILENCE raises eyebrows as Manoj Desai pays FULL salaries despite ZERO revenue for 2 monthsInquirer EntertainmentRussell Brand convicted of rape; judge says he’ll get prison termPunchNRS urges content creators to register businesses for tax reliefThe Jerusalem Post'You'll know soon enough': Trump leaves door open to devastating new Iran strikesCNN TürkFATMA BETÜL SAYAN KAYA KİMDİR, KAÇ YAŞINDA, NERELİ? AK Parti'deki Görevlerinden Neden Ayrıldı? İşte Hayatı ve Siyasi KariyeriSky TG24Lecce, sequestrata oltre 1 tonnellata di fuochi pirotecniciDaily MaverickSQUAD STRENGTH: Boks’ superior depth could prove decisive in Nations Championship title raceInquirerMarbled monitor lizard rescued, released into wild in BatanesZDF heuteAktuelle Pressemitteilungen des ZDFVanguardUS federal judge blocks Trump’s massive arch projectRapplerBojie Dy, the unexciting Speaker whom Marcos can count onHet Laatste NieuwsNa wekenlang teasen: Pommelien Thijs lost nieuwe single ‘Onomatopee’
The Daily Newsstand · Free, Always
Friday, November 6, 2026

Аутентификация «Dual JWT». Минимум ресурсов при высокой безопасности

Translate

В статье — вариант реализации стандарта OAuth 2.0 (RFC 6749).
Цель схемы «Dual JWT» — минимизировать потребление ресурсов инфраструктуры при сохранении достаточно высокого (но не максимального) уровня безопасности. Разберём, как устроена схема аутентификации, параметры её безопасности, а также экономику решения: за счёт чего схема снижает затраты (FinOps).
Суть схемы: access в заголовке + refresh в cookie, оба — stateless-JWT, плюс deny list в Redis для отзыва сессий.

Демо для FastAPI — dual-JWT-demo.py.

Нет времени читать целиком? Заключение и выводы — в §9.
§1–2 — для тех, кто хочет понять refresh: зачем он нужен и как его правильно использовать.
§3–5 — как устроена схема «Dual JWT»; §6–8 — её сильные и слабые стороны.

Содержание

1. Один токен — ущербный компромисс

1.1. Проблема одной куки (session ID)

Cookie прикладывается браузером автоматически к каждому запросу на домен. Это создаёт CSRF: сторонний сайт может инициировать запрос от имени пользователя.

Проблема: одна кука — это выбор из трёх зол:

  • либо CSRF-уязвимость;

  • либо невозможность cross-site запросов;

  • либо неудобная CSRF-защита (CSRF-токен), усложняющая API-клиентов.

1.2. Проблема одного токена в заголовке

Токен передаётся в Authorization: Bearer. Браузер не прикладывает его автоматически → CSRF не применим. Cross-site запросы работают.

Проблема: один токен — это выбор из двух зол:

  • либо угроза XSS-кражи токена;

  • либо короткий TTL и логин каждые 15 минут.

1.3. Вывод

Схему аутентификации с одним токеном (кукой либо заголовком) допустимо применять для M2M (машина-машина), но не для браузера. В общем случае её не рекомендуется использовать по соображениям безопасности.

2. Модель «access/refresh»: два токена, две роли, два транспорта

Рекомендации семейства OAuth 2.0 (RFC 6749, 9700, 10017) описывают решение этой дилеммы с помощью двух токенов.

Токен

Роль

Время жизни

Способ отправки на сервер

Access

Доступ к API

короткий (5–15 мин)

в заголовке

Refresh

Обновление токенов

долгий (15–30 дней)

в cookie

  • Access — заголовок Authorization: Bearer.

    • CSRF не применим;

    • cross-site запросы работают;

    • XSS — кража возможна, но окно использования мало (5–15 мин).

  • Refresh — долгий (30 дней), HttpOnly cookie.

    • CSRF закрыт (SameSite=Strict + только POST + узкий path);

    • XSS не украдёт (HttpOnly).

Что решено:

  • CSRF — закрыт для refresh, не применим для access;

  • XSS — refresh не украдут, а украденный access живёт недолго (5–15 мин);

  • cross-site-запросы — работают (access в заголовке);

  • удобство:

    • Access в заголовке, явное обновление;

    • Refresh в куке, автоматическое обновление вместе с Access;

    • редкий логин.

Цена:

  • два токена вместо одного.

Преемственность, а не революция:

По сути refresh+access — это более удобная версия схемы session-cookie + CSRF-токен, с другим распределением обязанностей:

Аспект

session-cookie + CSRF-токен

refresh + access

Основная задача

credential + защита

credential + защита

Долгоживущий credential

session-cookie

refresh-cookie

Доступ к API

session-cookie

access-токен в заголовке

Защита от CSRF

CSRF-токен (double submit / synchronizer)

access в заголовке (вручную), refresh: SameSite=Strict

Защита от XSS (кража)

HttpOnly

refresh: HttpOnly, access: TTL

2.1. opaque или JWT

Стандарт OAuth 2.0 позволяет использовать токены форматов — и JWT, и opaque, как для access, так и для refresh. Opaque-токены реализованы через чтение/запись в БД. Для JWT достаточно проверки цифровой подписи без доступа к БД. В этой схеме используется пара JWT для оптимизации расходов на инфраструктуру за счёт снижения нагрузки на БД.

2.2. Access-токен

Формат: JWT.

Claims:

Claim

Назначение

iss

Issuer

aud

Audience

sub

user_id

jti

Уникальный ID токена (только для аудита)

iat

Время выпуска (UTC, секунды)

exp

Время истечения

typ

access

Хранение на клиенте: рекомендовано в памяти JS.
localStorage чуть хуже по безопасности, но допустимо, так как срок жизни токена — всего 5–15 минут.

Передача: Authorization: Bearer <access>.

Проверка: stateless — подпись, exp, aud, iss, typ, наличие iat, sub, jti. Допуск по времени JWT_LEEWAY.

2.3. Refresh-токен

Формат: JWT, криптография та же, что у access-токена.

Claims:

Claim

Назначение

iss

Issuer

aud

Audience

sub

user_id

jti

Уникальный ID токена (только для аудита)

sid

Session ID (идентификатор сессии)

iat

Время выпуска (UTC, секунды)

exp

Время истечения токена (скользящий TTL)

session_expiry

Абсолютный максимум сессии — 90 дней (фиксируется при логине, не меняется)

typ

refresh

Хранение на клиенте: HttpOnly + SameSite=Strict cookie.

Атрибуты cookie:

Атрибут

Значение

Зачем

HttpOnly

true

JS не читает → XSS не украдёт

Secure

true

Только HTTPS → MITM не перехватит

SameSite

Strict

CSRF не применим

Path

/auth/refresh

Только на refresh-эндпоинт

Max-Age

30 дней

Необходима ротация refresh-токена

Передача: браузер прикладывает cookie автоматически при запросе на /auth/refresh.

Проверка: stateless — подпись, exp, aud, iss, session_expiry, typ, наличие iat, sub, jti, sid. Допуск по времени JWT_LEEWAY.

2.4. Вывод

Схема «access + refresh» на двух транспортах снимает риски CSRF и XSS-кражи refresh; XSS-кража access ограничена коротким TTL. Выпуск и проверка токенов формата JWT не требуют БД.

Проверка отзыва токенов — отдельная задача, рассматривается в §4.

3. Эфемерная сессия и отказ от reuse detection

3.1. Сессия и ротация refresh

Refresh-токен можно продлевать в пределах SESSION_ABSOLUTE_TTL, выписывая новый.

Сессия (sid) — цепочка refresh-токенов от одного логина.

user (sub)
└── session (sid)
    ├── refresh T1 (ротирован)
    ├── refresh T2 (ротирован)
    └── refresh T3 (текущий)

Refresh-токен является промежуточным звеном между сессией и access-токеном.

Время неактивности сессии ограничено сроком жизни последнего refresh-токена (30 дней).

Поскольку в схеме используется refresh-JWT, саму сессию можно не хранить и не искать в БД — это опять же снижение нагрузки.

3.2. Отказ от reuse detection

Но у ротации есть побочный эффект: при обновлении refresh предыдущий токен остаётся валидным, и украденный refresh даёт доступ к API. Даже разовая кража refresh (а она редко бывает разовой) даёт злоумышленнику рабочий токен.

Механизм безопасности reuse detection требует хранить погашенные или, наоборот, активные refresh: при ротации старый токен помечается погашенным (выведенным из оборота). Попытка использовать погашенный refresh → инцидент, отзыв сессии.

При использовании reuse detection возникает проблема состояния гонки между вкладками браузера, что решается grace-окном с ответом 401 при попытке использовать погашенный refresh, но без эскалации ИБ-инцидента.

Одновременно с этим появляется проблема потери ответа refresh в условиях нестабильной (мобильной) связи клиентов. При возникновении такой ситуации клиент не сможет использовать ни новый, ни старый токен и будет вынужден пройти процедуру логина заново, что ухудшает UX.

reuse detection требует существенных затрат инфраструктуры на хранение и проверку токенов с использованием БД и распределённого кэша.

В данной схеме аутентификации механизм безопасности reuse detection не используется.

Плюсы:

  • ни выпущенные, ни погашенные refresh не храним, не пишем, не читаем — быстрее, дешевле, масштабируется;

  • не надо решать проблему состояния гонки на клиенте;

  • нет проблемы потери ответа refresh — лучше UX.

Минус:

  • старый refresh валиден до своего exp, в одной сессии одновременно живут несколько refresh.

Почему компромисс приемлем:

  • HttpOnly + SameSite=Strict + Secure закрывают основные пути кражи refresh;

  • маловероятно, что у злоумышленника окажется только старый refresh: если украл один раз, скорее всего украдёт и актуальный.

Единственное исключение — инцидент ИБ

Обнаружили инцидент ИБ и устранили уязвимость — новый токен украсть уже не могут, а старый остаётся валидным. В этом случае мы явно отзываем его — через механизм отзыва сессий (см. §4) или смену ключей подписи.

Где нужна дополнительная защита:

  • повторный запрос пароля или 2FA (re-auth) для критичных операций.

3.3. Вывод

«Тяжёлые» stateful-проверки защищают от использования украденных токенов, но нужны не всем: для финансов и критичных данных — да, для контентных сервисов — часто избыточны. Во втором случае отказ от таких проверок приемлем и заметно снижает затраты на инфраструктуру.

4. Отзыв сессий (revocation)

Отзыв сессий требуется в следующих случаях:

  1. Пользователь подозревает проблемы безопасности и хочет самостоятельно завершить все сессии на всех устройствах.

  2. При смене пароля или другого фактора аутентификации: OWASP ASVS требует предоставлять пользователю опцию завершить другие сессии.

  3. Функция администратора/бэк-офиса — принудительное завершение сессий пользователя (например, при инциденте или блокировке учётной записи).

Функция завершения сессий есть у многих вендоров, но реализация различается:

  • Google, Netflix, Disney+, ChatGPT, Claude, Bitwarden — у всех есть явная кнопка «выйти на всех устройствах».

  • Microsoft — функция есть, но действует с задержкой до 24 часов.

  • Unity — функции нет; разлогинить все устройства можно только сбросом пароля.

Stateless-проверки не позволяют отозвать токены, а от «тяжёлых» запросов к БД мы отказались. Рассмотрим, как решить эту задачу дёшево.

4.1. Модель проверки refresh

Каждый JWT содержит время создания — iat (issued at).
На стороне сервера хранится граница отсечения по времени revocation_cutoff для пользователя.
При попытке использовать refresh-токены:

  • с iat <= revocation_cutoff токены считаются отозванными;

  • с iat > revocation_cutoff — токены валидны.

4.2. Хранилище — Redis

В случае отзыва всех сессий пользователя создаётся ключ:
user:<user_id>:revocation_cutoff → timestamp

Записи создаются только для пользователей, для которых запрашивался отзыв.

4.3. TTL ключа — 30 дней

Нет смысла ждать окончания сессии, достаточно дождаться истечения последнего refresh-токена, выпущенного перед отзывом.

TTL ключа (REDIS_REVOCATION_STORE_TTL) равен максимальному TTL среди всех активных refresh (REFRESH_SLIDING_TTL). Надо быть аккуратным при изменении этих настроек на проде.

4.4. Стратегии проверки отзыва access

Выбор стратегии проверки отзыва access зависит от контекста применения.

Стратегия A — локальный кэш revocation_cutoff.
Локальный кэш — это копия всех записей revocation_cutoff в памяти каждого инстанса веб-приложения.

Подходит, если контекст не предполагает отзыв пользователем (M2M, сервисные интеграции, внутренние системы без интерактивного logout).
Тогда отзывов либо нет, либо они настолько редки, что полностью умещаются в локальный кэш.
В результате убираем сетевой вызов Redis при проверке обоих токенов, получаем ускорение проверки и снижение нагрузки на инфраструктуру. Это экономия без ущерба безопасности.

Стратегия B — отзыв только для refresh, access TTL = 5 минут.

Подходит, если контекст предполагает отзыв пользователем (интерактивные приложения, где возможны «выйти везде» и смена пароля).
В общем случае локально кэшировать revocation_cutoff не получится: интенсивность потока отзывов определяется пользователем, и гарантии, что все записи поместятся в кэш, нет.
Поэтому проверка отзыва для access не выполняется вовсе: access живёт до 5 минут (вместо 15 минут для стратегии A), отзыв проверяется только для refresh (раз в 5 минут, а не на каждый запрос к API).
Это компромисс по безопасности в угоду производительности, который компенсируется коротким TTL access.

4.5. Вывод

Граница отсечения не позволяет отозвать отдельную сессию. Зато все сессии пользователя отзываются одной операцией записи.

Стратегия проверки отзыва access выбирается под контекст: локальный кэш — там, где пользовательского отзыва нет; короткий TTL без проверки access — там, где отзыв есть.

Контекст

Проверка отзыва access

Проверка отзыва refresh

Безопасность

Без пользовательского отзыва

явно: локальный кэш

через локальный кэш

без ущерба безопасности

С пользовательским отзывом

неявно: access TTL 5 мин

через Redis

нет мгновенного отзыва access, но TTL сокращается до 5 мин

При проверке отзыва чтение из Redis быстрое, а при редких отзывах нагрузка на него минимальна.

5. Как это работает вместе: жизненный цикл

5.1. Логин

  1. Пользователь аутентифицируется (email + пароль).

  2. Сервер выпускает access + refresh.

  3. Refresh → в cookie.

  4. Access → в теле ответа.

5.2. Доступ к API

  1. Клиент отправляет запрос к защищаемому API с заголовком Authorization: Bearer <access>.

  2. Сервер читает access из заголовка и проверяет JWT (подпись, aud, iss, exp, typ).

  3. Ошибка проверки → 401.

  4. Дополнительно, если включена проверка отзыва через локальный кэш и при этом iat <= revocation_cutoff → 401 (сессия отозвана).

  5. Проверка пройдена → payload доступен обработчику эндпоинта, sub — пользователь запроса.

5.3. Refresh (ротация токенов)

Когда срок действия access-токена подходит к концу:

  1. Клиент отправляет POST /auth/refresh (cookie прикладывается автоматически).

  2. Сервер читает refresh из cookie и проверяет JWT (подпись, exp, aud, iss, session_expiry, iat, typ).

  3. Ошибка проверки → 401.

  4. Дополнительно, если session_expiry <= now → 401 (сессия истекла).

  5. Дополнительно, если iat <= revocation_cutoff → 401 (сессия отозвана).

  6. Ротация refresh-токена — на каждом вызове: выпуск нового (те же sid и session_expiry, новые jti и exp), установка cookie.

  7. Возврат нового access.

5.4. Logout (текущая сессия)

  1. Клиент должен немедленно забыть (удалить) access.

  2. Клиент отправляет POST /auth/logout.

  3. Сервер удаляет refresh-cookie.

  4. access истечёт через TTL (5–15 мин).

5.5. Logout-all (все сессии)

Закрыть все сессии пользователя на всех устройствах:

  1. Клиент отправляет POST /auth/logout-all.

  2. Сервер записывает revocation_cutoff = now для user_id в Redis.

  3. Все refresh-токены этого пользователя с iat <= revocation_cutoff отклоняются (401).

  4. Если проверка отзыва access включена (стратегия A) и при этом его iat <= revocation_cutoff → 401 (мгновенный отзыв через локальный кэш).

  5. Если не включена (стратегия B), access истечёт через TTL (5 мин).

6. Производительность и FinOps

В этой схеме производительность и стоимость оптимизируются одновременно:
быстрее → меньше ресурсов → дешевле.

Этот раздел — выжимка из предыдущих глав про производительность: что делаем и что получаем.

6.1. Принцип: быстрее = дешевле

Исключение каждой stateful-проверки (обращение к БД или Redis) экономит не только миллисекунды, но и инфраструктуру, которую необходимо масштабировать под RPS. Выигрыш складывается из трёх источников:

  1. Меньше I/O — проверка подписи JWT не требует записи/чтения из БД.

  2. Меньше состояния — меньше хранить, бэкапить, реплицировать, восстанавливать.

  3. Меньше масштабирования — при росте RPS не нужно масштабировать хранилище.

6.2. Меры оптимизации

  1. JWT вместо opaque: тратим CPU вместо I/O.

  2. Отзыв токенов через revocation_cutoff: храним только один ключ на пользователя и только в случае явного отзыва.

  3. Проверка revocation_cutoff обоих токенов через локальный кэш (стратегия A) или проверка только refresh через Redis (стратегия B).

  4. Отказ от хранения сессий.

  5. Отказ от reuse detection.

6.3. Что выигрываем

  • не нужны БД и распределённый кэш для хранения токенов и сессий;

  • меньше сетевых задержек;

  • меньше данных нужно хранить;

  • O(1) отзыв всех сессий пользователя;

  • проверки выполняются быстрее — лучше UX;

  • масштабирование требуется только для CPU (гибче и проще);

  • нагрузка на Redis при проверке отзыва отсутствует (стратегия A) или низкая (стратегия B);

  • инфраструктура дёшевле.

6.4. Итог

С точки зрения пользователя: проверки выполняются быстрее.

Инфраструктура:

  • кластер уровня веб-приложения (k8s или instance group);

  • кластер Redis для хранения revocation_cutoff;

  • не надо разворачивать и масштабировать БД и распределённый кэш для токенов и сессий;

  • минимум точек отказа.

С точки зрения разработки: меньше инфраструктуры — проще разработка, тестирование и поддержка.

В деньгах:

  • размер кластера Redis минимальный, зависит только от интенсивности отзыва сессий;

  • меньше инфраструктуры — меньше затрат;

  • масштабировать достаточно только CPU веб-приложения;

  • меньше затрат на разработку и поддержку.

Ограничение:

  • локальный кэш эффективен, только пока записей отзыва мало; иначе — больше запросов в Redis. Некритично, но снижает эффективность;

  • схема выгодна при высоком RPS и редких отзывах.

7. Безопасность схемы целиком

Проверка токенов состоит из двух этапов: проверка JWT и проверка отзыва.

7.1. CSRF

Защита Refresh-токена:

  • SameSite=Strict блокирует cookie при cross-site;

  • /auth/refresh — только POST;

  • path=/auth/refresh — cookie не уходит на другие эндпоинты.

Защита Access-токена:

  • не уязвим.

Результат:

  • CSRF-атаки через <form>, <img>, fetch не работают.

Где нужна дополнительная защита:

  • поддомены: SameSite=Strict не различает поддомены (например, evil.app.example.com), поэтому нужен дополнительный контроль (проверка заголовков Origin/Referer на /auth/refresh, изоляция поддоменов на уровне DNS/инфраструктуры).

7.2. XSS

Защита Refresh-токена:

  • HttpOnly — XSS не украдёт refresh.

Защита Access-токена:

  • короткий TTL.

Ограничения:

  • XSS может использовать refresh, пока страница открыта (same-origin запрос);

  • XSS может использовать access из памяти.

Где нужна дополнительная защита:

  • CSP, Trusted Types — XSS не выполнится.

7.3. MITM

Защита: HTTPS + Secure cookie + HSTS.

7.4. Кража устройства

Защита:

  • logout-all — отзыв всех сессий со всех устройств.

Где нужна дополнительная защита:

  • re-auth (повторный запрос пароля или 2FA) для критичных операций.

7.5. Reuse detection

Не реализован. Осознанный компромисс.

Причина:

  • потеря ответа при ротации → повторный логин → плохой UX при мобильной связи;

  • необходимость stateful-проверки — дополнительные значительные ресурсы на инфраструктуру.

Где нужна дополнительная защита:

  • re-auth для критичных операций;

  • logout-all при компрометации.

7.6. Соответствие стандартам и рекомендациям

Из OAuth 2.0 / OIDC взяты модель access/refresh и меры ИБ.

Соответствие:

Стандарт / рекомендация

Что требует

Как в схеме

Статус

RFC 6750 (Bearer)

access в Authorization: Bearer

access в заголовке

✅

RFC 7519 / 7515–7518 (JWT/JWS)

claims iss, aud, sub, iat, exp + подпись

все claims пишутся и проверяются

✅

RFC 8725 (JWT BCP)

явный белый список alg, исключить none и подмену HS/RS

algorithms=[JWT_ALGORITHM]

✅

RFC 8725; RFC 7519

обязательная проверка iss, aud, exp; отказ при отсутствии обязательных claims

проверка всех claims + exp.leeway

✅

RFC 8725

разделить назначение токена (защита от token confusion)

кастомный claim typ (access/refresh) — refresh не принимается вместо access

✅

RFC 6265 / 6265bis (cookies)

HttpOnly, Secure, SameSite, узкий Path

refresh-cookie: HttpOnly + Secure + SameSite=Strict + Path=/auth/refresh + Max-Age

✅

OAuth 2.0 Security BCP (RFC 9700)

ротация refresh

реализована

✅

OWASP ASVS / Session Management Cheat Sheet

конечное время жизни сессии + idle timeout

скользящий exp + session_expiry (90 дней)

✅

OWASP

refresh не читается клиентом

HttpOnly cookie

✅

OAuth 2.0 Security BCP (RFC 9700)

reuse detection / sender-constrained refresh

не реализовано

⚠ осознанный компромисс

Итого: обязательные меры (подпись, белый список alg, iss/aud/exp, cookie-флаги, ограниченное время жизни сессии) выполнены. Не реализован только reuse detection — осознанный компромисс; последствия ограничены re-auth для критичных операций и logout-all при компрометации.

7.7. Дополнительные меры безопасности

  • Rate limiting на /auth/refresh: Ограничение частоты запросов на эндпоинт обновления токенов (по IP, по sub, по sid).

  • Re-auth для критичных операций: Требование повторной аутентификации (пароль, MFA) перед чувствительными действиями: смена пароля, email, отключение MFA, привязка платёжных данных и т. д. — это ограничивает ущерб от компрометации сессии.

  • Мониторинг аномалий поведения токенов: Отслеживание отклонений в паттернах использования токенов: аномальные IP/ASN, время активности, объём запросов, доступ к нехарактерным ресурсам. Позволяет обнаружить компрометацию до истечения TTL.

  • Завершение прочих сессий при смене учётных данных: Автоматический logout-all после смены пароля, email или факторов MFA. Гарантирует, что устаревшие сессии не переживут смену аутентификационных данных.

  • Device Bound Session Credentials (DBSC) для refresh-куки: Аппаратная привязка сессионной куки к устройству. В отличие от HttpOnly и SameSite, которые защищают только от XSS/CSRF, DBSC закрывает сценарий кражи куки вредоносным ПО с диска или из памяти браузера. При логине браузер генерирует пару ключей, приватный ключ сохраняется в TPM (Windows) или Secure Enclave (macOS) и не может быть извлечён даже вредоносным ПО с правами пользователя. Сервер привязывает сессию к публичному ключу. Refresh-кука становится короткоживущей (например, 10 минут), и браузер автоматически обновляет её, подписывая challenge этим аппаратным ключом. Если злоумышленник украдёт куку и попытается использовать её на другой машине, следующий цикл обновления провалится — у него нет доступа к приватному ключу.

8. Сравнение с индустрией

8.1. Сравнение с Google

Аспект

Google

Эта схема

Access

opaque, 1 час

JWT, 15 мин

Проверка access

introspection

stateless

Refresh

opaque

JWT, 30 дней

Rotation

опционально

да

Reuse detection

при rotation

нет

Абсолютный максимум сессии

месяцы/годы

90 дней

Logout

БД

удалить cookie

Logout-all

БД

Redis: revocation_cutoff

БД

все токены и сессии

нет

Redis

кэш БД

только revocation_cutoff

Общее:

  • короткий access, долгий refresh;

  • logout-all;

  • абсолютный максимум сессии.

Различия:

  • Google: opaque, introspection, мгновенный отзыв;

  • Эта схема: JWT, stateless, отзыв через revocation_cutoff.

8.2. Другие вендоры

Вендор

Access

Refresh

БД

Redis

Reuse detection

AWS Cognito

JWT

opaque

sessions, refresh

нет

да

Keycloak

JWT

opaque

sessions, refresh

кэш

да

Supabase

JWT

opaque

sessions, refresh

нет

да

Ory Hydra

opaque / JWT

opaque

sessions, refresh, access (HMAC)

кэш

да

Эта схема

JWT

JWT

нет

1 запись на logout-all

нет

Все вендоры в таблице используют opaque-токены и БД для их хранения.

8.3. Библиотеки

Библиотека

Язык

Access

Refresh

БД

Redis

Reuse detection

Flask-JWT-Extended

Python

JWT

JWT

нет

нет

нет

Spring Authorization/Resource Server

Java

opaque / JWT

opaque / JWT

да/нет — зависит от настроек токенов

опционально

опционально

django-oauth-toolkit

Python

opaque / JWT

opaque

OAuth models

кэш

да

passport-jwt

JS

JWT

нет

нет

нет

нет

express-jwt

JS

JWT

нет

нет

нет

нет

Эта схема

Python (demo)

JWT

JWT

нет

1 запись на logout-all

нет

Flask-JWT-Extended — наиболее близкая реализация; требуется добавить проверку отзыва через встроенный token_in_blocklist_callback.
Аналогично для Spring Authorization/Resource Server — это универсальный комбайн. С его помощью можно реализовать разные схемы аутентификации, в том числе рассмотренную в статье.

8.4. Вывод

Эти три таблицы показывают, что большинство вендоров выбирают stateful-путь — им нужен контроль сессий и reuse detection. Схема Dual JWT — противоположный выбор: stateless + revocation_cutoff, отказ от reuse detection ради UX и экономии ресурсов. Это не «лучше» и не «хуже» — это другой компромисс под другой сценарий.

9. Заключение: кому подходит схема

Один токен для браузерного приложения — плохой выбор: кука уязвима к CSRF, токен в заголовке — к XSS-краже. Схема «access в заголовке + refresh в куке» закрывает оба риска.
Кроме того, OAuth 2.0 рекомендует отзыв токенов/сессий и reuse detection для дополнительного уровня безопасности.
Дальше — развилка. reuse detection нужен не везде: для критичных областей — обязателен, для остальных — нет. Он требует инфраструктуры хранения токенов или сессий и ухудшает UX — риск разлогинов при потере связи. В менее критичных областях эту цену платить не обязательно. Отсюда выбор: держать stateful-инфраструктуру и мириться с разлогинами — или отказаться от reuse detection осознанно.

Схема Dual JWT идёт вторым путём. JWT для обоих токенов, revocation_cutoff для проверки отзыва и отказ от reuse detection позволяют полностью исключить БД: остаются CPU веб-приложения и Redis для хранения revocation_cutoff. Причём размер и нагрузка на Redis зависят скорее от того, как часто происходит отзыв всех сессий (logout-all), чем от RPS. В итоге повышается производительность аутентификации.

Положительные эффекты: лучше UX (быстрее, стабильнее — меньше разлогинов) и дешевле инфраструктура (FinOps).

Обратная сторона: нельзя отозвать отдельную сессию — только все сразу (logout-all). Отказ от reuse detection повышает риски для критичных сервисов — компенсация: re-auth для отдельных операций и logout-all при компрометации.

Применимость схемы

Сначала — о безопасности: рекомендуемые меры по OAuth 2.0 выполнены, единственное исключение — reuse detection, для которого предложена компенсация.

Схема рассчитана на высокий RPS и редкие отзывы: там выигрыш от stateless-проверки максимален, а нагрузка на инфраструктуру проверки отзывов минимальна.
Схема подходит для хайлоада, FinOps-ориентированных команд и сценариев, где важен UX (нестабильная связь не должна провоцировать разлогины).
Не подходит для банков и работы с критичными данными — случаев, где reuse detection обязателен — там нужен stateful-путь.
При частых отзывах (например, при каждой смене пароля) преимущество схемы снижается.

Схема Dual JWT — не про «лучше/хуже» stateful-решений (Keycloak, Cognito, Google), а про другой компромисс между безопасностью, стоимостью и удобством. Stateful-путь даёт reuse detection ценой нагрузки на БД; Dual JWT даёт дешёвую stateless-инфраструктуру и лучший UX ценой отказа от этих механизмов. Выбор определяется тем, что важнее в конкретном сценарии.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.