Обработка истёкших токенов

В спецификации JWT (JSON Web Token) срок жизни токена контролируется через стандартное поле exp (expiration time). Оно хранит метку времени в формате Unix timestamp и определяет момент, после которого токен считается недействительным.

В библиотеке Jose проверка этого поля выполняется автоматически при верификации подписи JWT, если используется функция проверки, ориентированная на JWS, например jwtVerify.

Основная логика обработки истечения срока действия строится вокруг двух аспектов:

  • сравнение текущего времени с exp
  • поведение при обнаружении просрочки

Поведение Jose при проверке exp

При вызове:

import { jwtVerify } from 'jose'

const { payload } = await jwtVerify(token, secret)

библиотека выполняет несколько шагов:

  1. Проверяет криптографическую подпись токена
  2. Извлекает полезную нагрузку (payload)
  3. Сравнивает текущее время с полем exp

Если текущий timestamp превышает значение exp, выполнение прерывается и выбрасывается исключение.

Важный момент: проверка времени выполняется автоматически и не требует ручной реализации.


Тип ошибки JWTExpired

При истечении срока действия токена Jose генерирует специализированную ошибку:

  • JWTExpired

Она наследуется от базового класса ошибок JWT и содержит дополнительную информацию о причине сбоя.

Пример обработки:

import { jwtVerify, errors } from 'jose'

try {
  const { payload } = await jwtVerify(token, secret)
} catch (err) {
  if (err instanceof errors.JWTExpired) {
    // токен просрочен
  }
}

Объект ошибки может содержать:

  • payload — декодированные данные токена (если доступны)
  • message — текстовое описание причины
  • code — тип ошибки

Clock tolerance и расхождение времени

Реальные системы часто сталкиваются с проблемой рассинхронизации времени между клиентом и сервером. Даже небольшая разница в несколько секунд может приводить к ложному срабатыванию истечения токена.

Для решения этой проблемы в Jose предусмотрен параметр clockTolerance.

await jwtVerify(token, secret, {
  clockTolerance: '10s'
})

Значение задаёт допустимое отклонение времени в обе стороны. Это означает:

  • токен не будет считаться истёкшим, если превышение exp находится в пределах допуска
  • уменьшается вероятность ошибок из-за NTP-десинхронизации

Поле exp и его корректное формирование

При создании токена важно корректно задавать время жизни:

import { SignJWT } from 'jose'

const token = await new SignJWT({ userId: 123 })
  .setProtectedHeader({ alg: 'HS256' })
  .setExpirationTime('2h')
  .sign(secret)

Поддерживаются форматы:

  • числовой Unix timestamp
  • строковые выражения ("2h", "30m", "1d")

Некорректно заданный exp приводит к немедленной инвалидности токена или ошибке при валидации.


Обработка истёкших токенов в архитектуре системы

В прикладных системах истечение токена не рассматривается как исключительная ситуация, а является частью нормального жизненного цикла авторизации.

Типовой поток обработки:

  1. Попытка доступа с access token
  2. Получение JWTExpired
  3. Проверка наличия refresh token
  4. Запрос нового access token
  5. Повтор исходного запроса

Важно, что логика обновления токена не должна выполняться внутри Jose — библиотека отвечает только за криптографическую проверку и валидацию структуры.


Разделение access и refresh токенов

Истечение срока действия чаще всего применяется только к access token.

Refresh token:

  • имеет более длительный срок жизни
  • может проверяться отдельной логикой
  • часто хранится в базе данных или Redis

Такое разделение снижает риск компрометации системы, ограничивая время жизни активного доступа.


Преднамеренно истёкшие токены и revoke-практики

Поле exp не решает задачу принудительной отзыва токена до его истечения. Для этого применяются дополнительные механизмы:

  • blacklist токенов (по jti)
  • версии токена в базе пользователя
  • инвалидизация refresh token

Jose при этом остаётся на уровне проверки подписи и времени, не управляя состоянием токена.


Проверка exp без верификации подписи

В некоторых сценариях требуется только декодирование payload без проверки подписи:

import { decodeJwt } from 'jose'

const payload = decodeJwt(token)

В этом случае:

  • поле exp доступно
  • но не проверяется автоматически
  • ответственность за проверку ложится на прикладной код

Такой подход используется редко и только для некритичных операций (например, отображение информации до полной проверки).


Безопасные стратегии работы с истечением токена

Корректная обработка истёкших токенов обычно включает несколько уровней защиты:

  • обязательная проверка через jwtVerify
  • минимизация времени жизни access token
  • использование clockTolerance для устранения расхождений времени
  • централизованная обработка JWTExpired
  • разделение ролей access и refresh токенов
  • возможность немедленной инвалидизации через серверное состояние

Такая комбинация позволяет избежать как ложных отказов доступа, так и использования устаревших токенов в распределённых системах