В библиотеке 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При тестировании необходимо покрывать следующие сценарии:
nbfnbf = now + 10
clockTolerance = 0
Ожидаемый результат: ошибка
nbf = now + 3
clockTolerance = 5
Ожидаемый результат: успешно
nbf = now + 10
clockTolerance = 5
Ожидаемый результат: ошибка
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 выполняет обе проверки:
nbfexpС учётом clockTolerance:
currentTime + tolerance >= nbf
currentTime - tolerance <= exp
Использование реального времени приводит к flaky-тестам:
Причина — миллисекундные расхождения
Частая ошибка — считать, что clockTolerance влияет
только на exp. На практике он применяется ко всем временным
полям.
JWT всегда использует Unix timestamp (UTC). Ошибки возникают при:
Date без нормализацииФиксация времени
nowDate.now() в тестахПокрытие крайних значений
Обязательные кейсы:
nbf = nownbf = now ± 1nbf = now ± clockTolerancenbf = now ± (clockTolerance + 1)Тестирование с разными tolerance
0Проверка поведения при отрицательных значениях
Некорректные токены:
nbf = -1000
Ожидается отклонение или обработка как прошедшего времени.
Использование clockTolerance — компромисс между:
Слишком большое значение:
Слишком маленькое:
При валидации токена библиотека jose использует
расширенные условия:
токен считается валидным, если:
nbfexpЭто делает систему устойчивой к реальным условиям эксплуатации, но требует тщательного тестирования всех граничных сценариев.