Как минимизировать риск кражи JWT токена
Что проверяют
Понимаете ли вы, что JWT это токен на предъявителя: кто его украл, тот и авторизован, пока не истечёт срок. Интервьюер хочет услышать про хранение, срок жизни, отзыв и проверку на сервере. Следом обычно спрашивают, как отозвать токен досрочно, если JWT по задумке не хранит состояния.
Как отвечать
Разложите защиту по слоям:
- короткий access-токен: от 5 до 15 минут, плюс refresh-токен для обновления. Украденный access быстро становится бесполезным;
- ротация refresh-токена: при каждом обновлении выдаётся новый, старый гасится. Повторное предъявление старого означает утечку, и тогда отзывается вся цепочка. В
djangorestframework-simplejwtэто настройкиROTATE_REFRESH_TOKENSиBLACKLIST_AFTER_ROTATION; - хранение: refresh в cookie с флагами
HttpOnly,Secure,SameSite, access в памяти приложения, а не вlocalStorage, откуда его достанет любой XSS. Во FastAPI этоresponse.set_cookie(..., httponly=True, secure=True, samesite="lax"); - CSRF: раз токен едет в cookie, нужна защита от подделки запросов, то есть
SameSiteплюс CSRF-токен для изменяющих запросов; - строгая проверка: в PyJWT
jwt.decode(token, key, algorithms=["RS256"], audience=..., issuer=...). Явный список алгоритмов закрывает атаки сalg: noneи подменой RS256 на HS256, заодно проверяютсяexp,aud,iss; - отзыв: список отозванных
jtiв Redis с TTL до истечения токена или номер версии токена в профиле пользователя; - привязка к клиенту: DPoP или mTLS делают украденный токен бесполезным без закрытого ключа клиента. Это уже уровень финансовых API.
И фон: HTTPS с HSTS, CSP против XSS, никаких токенов в URL и логах.
Где спотыкаются
Говорят, что JWT зашифрован. Обычный JWS только подписан, полезная нагрузка читается декодированием base64url, поэтому класть туда персональные данные нельзя.
Выдают access-токен на сутки или неделю и не предусматривают отзыв. Украденный токен тогда живёт до конца срока, и сделать с ним ничего нельзя.
Переносят токен в cookie и забывают про CSRF. Кражу через XSS закрыли, зато открыли подделку запросов с чужого сайта.