Тестирование граничных случаев: clockTolerance, nbf

В библиотеке jose особое внимание уделяется корректной обработке временных полей токена: exp, iat, nbf. Ошибки в их интерпретации часто приводят к уязвимостям или ложным отказам в авторизации. Граничные случаи возникают из-за расхождения системного времени, сетевых задержек и особенностей генерации токенов.

Ключевые параметры:

  • nbf (Not Before) — момент времени, раньше которого токен считается недействительным
  • exp (Expiration Time) — момент, после которого токен считается просроченным
  • iat (Issued At) — время выпуска токена
  • clockTolerance — допустимое отклонение системного времени при валидации

Поле nbf: особенности и граничные ситуации

Поле nbf защищает от преждевременного использования токена. Оно особенно важно в сценариях, где токен выпускается заранее, но должен начать действовать позже (например, отложенные сессии или scheduled jobs).

Базовая проверка

При верификации jose сравнивает текущее время с nbf:

import { jwtVerify } from 'jose'

await jwtVerify(token, publicKey)

Если текущее время меньше nbf, выбрасывается ошибка.

Граничные случаи

1. Точное совпадение времени

Если текущее время равно nbf, токен считается валидным. Это важно учитывать при тестировании:

nbf = now

Токен должен успешно проходить проверку.


2. Отставание системного времени

Если сервер отстает даже на несколько секунд, валидный токен может быть отклонён:

nbf = now + 5 секунд

На сервере с отставанием токен будет считаться «ещё не активным».


3. Погрешности при генерации

Если токен создаётся с округлением времени (например, до секунд), возможны ситуации:

  • клиент считает токен валидным
  • сервер — ещё нет

Использование clockTolerance

Для компенсации расхождений времени используется параметр clockTolerance.

Он задаёт допустимое отклонение (в секундах), в пределах которого проверка считается успешной.

Пример

await jwtVerify(token, publicKey, {
  clockTolerance: 5 // секунд
})

Это означает:

  • токен с nbf на 5 секунд в будущем будет принят
  • токен с exp на 5 секунд в прошлом — тоже

Как clockTolerance влияет на nbf

Фактически проверка становится:

currentTime + clockTolerance >= nbf

То есть токен принимается немного раньше указанного времени.


Граничные тесты для nbf

При тестировании необходимо покрывать следующие сценарии:

1. Токен строго до nbf

nbf = now + 10
clockTolerance = 0

Ожидаемый результат: ошибка


2. Токен в пределах tolerance

nbf = now + 3
clockTolerance = 5

Ожидаемый результат: успешно


3. Токен вне tolerance

nbf = now + 10
clockTolerance = 5

Ожидаемый результат: ошибка


4. Точное попадание в границу tolerance

nbf = now + 5
clockTolerance = 5

Ожидаемый результат: успешно


Практика тестирования

Генерация токенов с контролируемыми временными метками

import { SignJWT } from 'jose'

const token = await new SignJWT({ data: 'test' })
  .setProtectedHeader({ alg: 'HS256' })
  .setNotBefore('10s')
  .sign(secret)

Подмена времени в тестах

Для точного тестирования граничных условий используется мок времени:

const now = Math.floor(Date.now() / 1000)

const token = await new SignJWT({})
  .setNotBefore(now + 10)
  .sign(secret)

Взаимодействие nbf и exp

Граничные ошибки часто возникают при некорректной комбинации:

nbf > exp

Такой токен:

  • никогда не станет валидным
  • должен отклоняться сразу

Проверка с учётом обеих границ

jose выполняет обе проверки:

  1. nbf
  2. exp

С учётом clockTolerance:

currentTime + tolerance >= nbf
currentTime - tolerance <= exp

Ошибки, возникающие при тестировании

1. Нестабильные тесты

Использование реального времени приводит к flaky-тестам:

  • тест иногда проходит
  • иногда падает

Причина — миллисекундные расхождения


2. Неправильная интерпретация tolerance

Частая ошибка — считать, что clockTolerance влияет только на exp. На практике он применяется ко всем временным полям.


3. Игнорирование часовых поясов

JWT всегда использует Unix timestamp (UTC). Ошибки возникают при:

  • локальных преобразованиях времени
  • использовании Date без нормализации

Рекомендации по тестированию

Фиксация времени

  • использовать фиксированное значение now
  • избегать Date.now() в тестах

Покрытие крайних значений

Обязательные кейсы:

  • nbf = now
  • nbf = now ± 1
  • nbf = now ± clockTolerance
  • nbf = now ± (clockTolerance + 1)

Тестирование с разными tolerance

  • 0
  • небольшие значения (1–5 секунд)
  • большие значения (30–60 секунд)

Проверка поведения при отрицательных значениях

Некорректные токены:

nbf = -1000

Ожидается отклонение или обработка как прошедшего времени.


Безопасность и компромиссы

Использование clockTolerance — компромисс между:

  • строгой безопасностью
  • устойчивостью системы

Слишком большое значение:

  • увеличивает окно атаки
  • позволяет использовать токены раньше/дольше

Слишком маленькое:

  • приводит к отказам при реальных задержках

Типичные значения

  • 0 секунд — строгая проверка
  • 2–5 секунд — стандартная практика
  • 10+ секунд — распределённые системы с нестабильным временем

Итоговая модель проверки

При валидации токена библиотека jose использует расширенные условия:

  • токен считается валидным, если:

    • текущее время с учётом tolerance не раньше nbf
    • текущее время с учётом tolerance не позже exp

Это делает систему устойчивой к реальным условиям эксплуатации, но требует тщательного тестирования всех граничных сценариев.