Инвалидировали ли старые токены при обновлении
Что проверяют
Понимаете ли вы, что stateless JWT нельзя отозвать без хранения состояния на сервере, и как вы решали это на практике. Следом обычно спрашивают, что будет, если украденным refresh-токеном воспользуется злоумышленник.
Как отвечать
Токены двух типов инвалидируются по-разному:
- access-токен: короткий JWT, живёт от 5 до 15 минут и проверяется по подписи без похода в базу. Отозвать его досрочно можно только с состоянием: denylist по
jtiв Redis с TTL до истечения токена или номер версии в claims, который сверяется сtoken_versionпользователя. Часто его сознательно не отзывают и просто ждут истечения; - refresh-токен: длинный, случайный и непрозрачный, генерируется через
crypto/rand. В базе лежит его хеш (SHA-256), а не сам токен, вместе с устройством и сроком.
Ключевая часть: ротация. При каждом обновлении сервер выдаёт новую пару, а старый refresh-токен помечает использованным. Если старый токен приходит повторно, это признак кражи: отзывается вся цепочка токенов этой сессии, и пользователь входит заново.
В Go это обычно github.com/golang-jwt/jwt/v5 с явным списком алгоритмов через jwt.WithValidMethods, а пометка использования атомарна: UPDATE refresh_tokens SET used_at = now() WHERE hash = $1 AND used_at IS NULL и проверка числа затронутых строк. Для веба refresh-токен лучше отдавать в куке с HttpOnly, Secure и SameSite.
Опирайтесь на свой проект: что хранили, как быстро отзывался доступ при выходе и блокировке. Цифр вроде «инцидентов стало на 30% меньше» без реального замера не приводите. Если ротации не было, скажите честно («старые токены жили до истечения срока»), объясните риск и как бы вы это исправили.
Где спотыкаются
Говорят «старый JWT инвалидировали», хотя проверка шла только по подписи. Без хранилища токен остаётся валидным до exp, что бы вы ни пометили в базе.
Не учитывают гонку: две вкладки одновременно отправляют один refresh-токен, второй запрос выглядит как повторное использование, и пользователя выкидывает. Помогает короткий льготный интервал или возврат уже выданной пары.
Хранят refresh-токены в базе открытым текстом. Утечка дампа сразу даёт доступ ко всем сессиям.