Безопасное хранение токенов
↑ 7.3 Безопасность фронтенда · ← Предыдущая · → Следующая
Зачем это на собесе
Один из самых обсуждаемых вопросов: «где хранить JWT». Нужно взвесить компромиссы.
Варианты
| Место | Плюсы | Минусы |
|---|---|---|
localStorage | просто | доступен любому скрипту → украдут через XSS |
sessionStorage | живёт до закрытия вкладки | та же XSS-уязвимость |
| Память (переменная) | недоступна после XSS-сессии в другой вкладке | теряется при перезагрузке |
Cookie HttpOnly; Secure; SameSite | JS токен не прочитает | нужна CSRF-защита |
Рекомендуемая схема
- access-токен — короткоживущий (5–15 минут), держим в памяти;
- refresh-токен — в
HttpOnly; Secure; SameSite=Strict/Laxcookie, путь/auth/refresh; - при перезагрузке страницы запрашиваем новый access по refresh;
- rotation refresh-токенов и детект повторного использования на сервере;
- выход: сервер отзывает refresh, cookie удаляется.
let accessToken: string | null = null
export async function refresh() {
const r = await fetch('/auth/refresh', { method: 'POST', credentials: 'include' })
accessToken = (await r.json()).accessToken
}OIDC и SPA
Для SPA: Authorization Code Flow с PKCE, без implicit flow. Библиотеки: oidc-client-ts, keycloak-js. Либо паттерн BFF (Backend for Frontend): токены живут на сервере, браузер получает только сессионную cookie.
Что ещё
- не класть чувствительные данные в JWT (он читаем, не шифруется);
- не логировать токены, скрывать из URL и
Referer; - короткий TTL и проверка
aud,iss,expна бэкенде.
Вопросы с ответами
Где хранить JWT в SPA?
Access-токен в памяти, refresh-токен в HttpOnly cookie с ротацией. localStorage уязвим для XSS. Наиболее защищённый вариант — BFF.
Зачем PKCE?
Защищает Authorization Code от перехвата в публичных клиентах: связывает запрос кода и обмен на токен одноразовым секретом.