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

В статье — вариант реализации стандарта 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 дней),
HttpOnlycookie.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: |
Защита от XSS (кража) |
| refresh: |
2.1. opaque или JWT
Стандарт OAuth 2.0 позволяет использовать токены форматов — и JWT, и opaque, как для access, так и для refresh. Opaque-токены реализованы через чтение/запись в БД. Для JWT достаточно проверки цифровой подписи без доступа к БД. В этой схеме используется пара JWT для оптимизации расходов на инфраструктуру за счёт снижения нагрузки на БД.
2.2. Access-токен
Формат: JWT.
Claims:
Claim | Назначение |
|---|---|
| Issuer |
| Audience |
| user_id |
| Уникальный ID токена (только для аудита) |
| Время выпуска (UTC, секунды) |
| Время истечения |
|
|
Хранение на клиенте: рекомендовано в памяти JS.localStorage чуть хуже по безопасности, но допустимо, так как срок жизни токена — всего 5–15 минут.
Передача: Authorization: Bearer <access>.
Проверка: stateless — подпись, exp, aud, iss, typ, наличие iat, sub, jti. Допуск по времени JWT_LEEWAY.
2.3. Refresh-токен
Формат: JWT, криптография та же, что у access-токена.
Claims:
Claim | Назначение |
|---|---|
| Issuer |
| Audience |
| user_id |
| Уникальный ID токена (только для аудита) |
| Session ID (идентификатор сессии) |
| Время выпуска (UTC, секунды) |
| Время истечения токена (скользящий TTL) |
| Абсолютный максимум сессии — 90 дней (фиксируется при логине, не меняется) |
|
|
Хранение на клиенте: HttpOnly + SameSite=Strict cookie.
Атрибуты cookie:
Атрибут | Значение | Зачем |
|---|---|---|
| true | JS не читает → XSS не украдёт |
| true | Только HTTPS → MITM не перехватит |
| Strict | CSRF не применим |
|
| Только на refresh-эндпоинт |
| 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)
Отзыв сессий требуется в следующих случаях:
Пользователь подозревает проблемы безопасности и хочет самостоятельно завершить все сессии на всех устройствах.
При смене пароля или другого фактора аутентификации: OWASP ASVS требует предоставлять пользователю опцию завершить другие сессии.
Функция администратора/бэк-офиса — принудительное завершение сессий пользователя (например, при инциденте или блокировке учётной записи).
Функция завершения сессий есть у многих вендоров, но реализация различается:
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 TTL 5 мин | через Redis | нет мгновенного отзыва access, но TTL сокращается до 5 мин |
При проверке отзыва чтение из Redis быстрое, а при редких отзывах нагрузка на него минимальна.
5. Как это работает вместе: жизненный цикл
5.1. Логин
Пользователь аутентифицируется (email + пароль).
Сервер выпускает access + refresh.
Refresh → в cookie.
Access → в теле ответа.
5.2. Доступ к API
Клиент отправляет запрос к защищаемому API с заголовком
Authorization: Bearer <access>.Сервер читает access из заголовка и проверяет JWT (подпись,
aud,iss,exp,typ).Ошибка проверки → 401.
Дополнительно, если включена проверка отзыва через локальный кэш и при этом
iat <= revocation_cutoff→ 401 (сессия отозвана).Проверка пройдена →
payloadдоступен обработчику эндпоинта,sub— пользователь запроса.
5.3. Refresh (ротация токенов)
Когда срок действия access-токена подходит к концу:
Клиент отправляет POST
/auth/refresh(cookie прикладывается автоматически).Сервер читает refresh из cookie и проверяет JWT (подпись,
exp,aud,iss,session_expiry,iat,typ).Ошибка проверки → 401.
Дополнительно, если
session_expiry <= now→ 401 (сессия истекла).Дополнительно, если
iat <= revocation_cutoff→ 401 (сессия отозвана).Ротация
refresh-токена — на каждом вызове: выпуск нового (те жеsidиsession_expiry, новыеjtiиexp), установка cookie.Возврат нового access.
5.4. Logout (текущая сессия)
Клиент должен немедленно забыть (удалить)
access.Клиент отправляет POST
/auth/logout.Сервер удаляет refresh-cookie.
accessистечёт через TTL (5–15 мин).
5.5. Logout-all (все сессии)
Закрыть все сессии пользователя на всех устройствах:
Клиент отправляет POST
/auth/logout-all.Сервер записывает
revocation_cutoff = nowдляuser_idв Redis.Все refresh-токены этого пользователя с
iat <= revocation_cutoffотклоняются (401).Если проверка отзыва
accessвключена (стратегия A) и при этом егоiat <= revocation_cutoff→ 401 (мгновенный отзыв через локальный кэш).Если не включена (стратегия B),
accessистечёт через TTL (5 мин).
6. Производительность и FinOps
В этой схеме производительность и стоимость оптимизируются одновременно:
быстрее → меньше ресурсов → дешевле.
Этот раздел — выжимка из предыдущих глав про производительность: что делаем и что получаем.
6.1. Принцип: быстрее = дешевле
Исключение каждой stateful-проверки (обращение к БД или Redis) экономит не только миллисекунды, но и инфраструктуру, которую необходимо масштабировать под RPS. Выигрыш складывается из трёх источников:
Меньше I/O — проверка подписи JWT не требует записи/чтения из БД.
Меньше состояния — меньше хранить, бэкапить, реплицировать, восстанавливать.
Меньше масштабирования — при росте RPS не нужно масштабировать хранилище.
6.2. Меры оптимизации
JWT вместо opaque: тратим CPU вместо I/O.
Отзыв токенов через
revocation_cutoff: храним только один ключ на пользователя и только в случае явного отзыва.Проверка
revocation_cutoffобоих токенов через локальный кэш (стратегия A) или проверка только refresh через Redis (стратегия B).Отказ от хранения сессий.
Отказ от
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 в | access в заголовке | ✅ |
RFC 7519 / 7515–7518 (JWT/JWS) | claims | все claims пишутся и проверяются | ✅ |
RFC 8725 (JWT BCP) | явный белый список |
| ✅ |
RFC 8725; RFC 7519 | обязательная проверка | проверка всех claims + | ✅ |
RFC 8725 | разделить назначение токена (защита от token confusion) | кастомный claim | ✅ |
RFC 6265 / 6265bis (cookies) |
| refresh-cookie: | ✅ |
OAuth 2.0 Security BCP (RFC 9700) | ротация refresh | реализована | ✅ |
OWASP ASVS / Session Management Cheat Sheet | конечное время жизни сессии + idle timeout | скользящий | ✅ |
OWASP | refresh не читается клиентом |
| ✅ |
OAuth 2.0 Security BCP (RFC 9700) |
| не реализовано | ⚠ осознанный компромисс |
Итого: обязательные меры (подпись, белый список 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
Аспект | Эта схема | |
|---|---|---|
Access | opaque, 1 час | JWT, 15 мин |
Проверка access | introspection | stateless |
Refresh | opaque | JWT, 30 дней |
Rotation | опционально | да |
Reuse detection | при rotation | нет |
Абсолютный максимум сессии | месяцы/годы | 90 дней |
Logout | БД | удалить cookie |
Logout-all | БД | Redis: |
БД | все токены и сессии | нет |
Redis | кэш БД | только |
Общее:
короткий 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 запись на | нет |
Все вендоры в таблице используют 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 запись на | нет |
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 ценой отказа от этих механизмов. Выбор определяется тем, что важнее в конкретном сценарии.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.