Как правильно организовать обновление пар JWT + RT в API для нескольких клиентов?
Потребовалось написать общий бэк (PHP) для нескольких SPA (TS+VUE) и мобилки. В качестве механизма авторизации был выбран подход с JWT и RT токенами.
Проблема возникла на стадии проектирования логики выдачи новой пары JWT+RT. Живут токены 30 минут и 15 дней соответственно. Проблема следующая: на SPA оба токена хранятся в httpOnly куке, отправляются с каждым запросом, на стороне сервера сначала проверяется JWT, потом RT-токен при каждом запросе, требующем аутентификации. Однако в SPA при обновлении страницы могут отправиться сразу несколько запросов на разные эндпоинты, которые зависят от этого самого токена (например, получение расширенной инфы по текущему пользователю и статус текущей закладки у книги). Соответственно, в ситуации, когда в куке JWT-токен протух, на сервер приходят сразу 2 запроса с одним и тем же refresh-токеном, первый обрабатывается корректно, а вот второй запрос аутентификацию не проходит, так как во время 1 запроса refresh-токен был заменён на другой. Как наиболее корректно решать эту проблему?
Я вижу следующие решения:
1) На клиенте, например, в localstorage запихивать timestamp срока, когда JWT протухает, соответственно, при каждом запросе смотреть на эту метку, и если она приближается к сроку / уже истекла, то отправлять запрос на выдачу новой пары на соответствующий эндпоинт. Но возникает проблема с надобностью в организации очереди запросов на клиенте.
2) Мудрить что-то со временем жизни refresh-токена и количеством его использований, но тогда в принципе нарушается идея: JWT - для доступа, RT - для выдачи JWT.
Больше мне в голову ничего не лезет, может, я что-то очевидно пропускаю?
VolgaVolga, для rt - вполне неплохой срок, разве нет? Я бы даже сказал, что маловато, ибо порой реально можно на сайт вернуться через 20 дней и странно было бы снова логиниться. А ротация обоих токенов ведь так и так каждые 30 минут происходит.
Для правильного вопроса надо знать половину ответа
В SPA в пределах одной страницы это реализуется примерно так:
var isRefreshing = null;
var refreshingCall = null;
async function request() {
while (true) {
if (isRefreshing) {
const refreshed = await refreshingCall;
isRefreshing = false;
}
const response = await fetch(...);
const data = await response.json();
if (!data.needRefresh) {
return data;
}
isRefreshing = true;
refreshingCall = doRefresh(...);
}
}
async function doRefresh(...) {
...
}
Основная идея - использование глобального флага обновления токена и глобальной переменной с промисом. Первый запрос, обнаруживший необходимость обновления, выставляет флаг и записывает промис, который возвращает функция обновления. Второй (и последующие) запрос видит, что флаг уже стоит и просто ждёт выполнения промиса.
Примерно такое же решение с флагами и предполагал, правда мне почему-то казалось, что есть какое-то оптимальное решение сугубо с серверной стороны, чтобы вообще не мучаться на клиенте. Что-то из разряда амортизирующих RT-токенов и такого подхода. Благодарю за ответ :)