Как реализовать JWT и refresh токены
Что проверяют
Для фронтендера главное не генерация токена на сервере, а жизнь токенов в браузере: где их хранить, как незаметно обновлять и что будет при XSS. Следом обычно спрашивают, что происходит, когда пять запросов одновременно получили 401.
Как отвечать
Коротко о схеме: access-токен живёт минуты и уходит в заголовке Authorization: Bearer, refresh-токен живёт дни или недели и нужен только для получения новой пары. Дальше то, за что отвечает клиент:
- хранение access: в памяти приложения (переменная модуля, стор). При перезагрузке он пропадает, и это нормально: на старте приложение один раз вызывает refresh;
- хранение refresh: cookie с флагами
HttpOnly,Secure,SameSite=StrictилиLaxиPath, ограниченным эндпоинтом обновления. JavaScript её не видит, значит XSS токен не украдёт; - обновление: перехватчик в axios (
interceptors.response) или обёртка надfetchловит 401, вызывает/auth/refreshи повторяет исходный запрос; - одно обновление на всех: пока идёт refresh, остальные запросы ждут тот же промис, а не запускают свой. Иначе при ротации второй запрос придёт со старым refresh-токеном и сессия оборвётся;
- ротация: сервер на каждый refresh выдаёт новый токен и отзывает старый, повторное использование старого считается кражей;
- вкладки и выход:
BroadcastChannelсообщает остальным вкладкам о новом токене или логауте; при выходе сервер отзывает refresh и очищает cookie.
Стоит упомянуть паттерн BFF: токены хранит собственный сервер приложения, а браузер получает только сессионную cookie. Для SPA с чувствительными данными именно этот вариант советует IETF в рекомендациях по OAuth для браузерных приложений.
Где спотыкаются
Кладут оба токена в localStorage. Любой внедрённый скрипт читает их одной строкой.
Считают, что cookie решает всё. С cookie появляется CSRF, и без SameSite или CSRF-токена запрос на обновление может отправить чужой сайт.
Читают payload на клиенте и принимают по нему решения о доступе. Клиент может декодировать exp, чтобы обновить токен заранее, но проверку подписи и прав делает только сервер.