NimuraF
@NimuraF

Как правильно организовать обновление пар 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.

Больше мне в голову ничего не лезет, может, я что-то очевидно пропускаю?
  • Вопрос задан
  • 92 просмотра
Решения вопроса 1
Rsa97
@Rsa97
Для правильного вопроса надо знать половину ответа
В 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(...) {
  ...
}
Основная идея - использование глобального флага обновления токена и глобальной переменной с промисом. Первый запрос, обнаруживший необходимость обновления, выставляет флаг и записывает промис, который возвращает функция обновления. Второй (и последующие) запрос видит, что флаг уже стоит и просто ждёт выполнения промиса.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы