Сообщение об ошибке
"exp" claim timestamp check failed
jose (JavaScript) — JWTExpired, code ERR_JWT_EXPIRED
Что это означает
Проверка утверждений в jose отклонила токен, потому что его метка времени exp по часам проверяющей стороны уже в прошлом. Проверку подписи токен к этому моменту прошёл: дело исключительно в его свежести. Для других утверждений jose выдаёт сообщение по тому же шаблону («"nbf" claim timestamp check failed»), когда токен используют раньше его времени not-before.
Почему это происходит
Срок жизни токена истёк
частоТокены доступа задуманы короткоживущими. Клиент, который кеширует токены, не проверяя срок действия, потребитель очереди, который обрабатывает сообщение с токеном через несколько часов после постановки в очередь, или тестовая фикстура с жёстко прописанным токеном неизбежно приводят к этой ошибке.
Часы проверяющей стороны спешат относительно часов издателя
частоУход часов на несколько минут на проверяющем хосте сокращает фактический срок жизни каждого токена и может приводить к отклонению только что выпущенных токенов. Контейнеры наследуют часы хоста — виртуальная машина с уплывающими часами ломает сразу все поды на ней.
В exp записаны не те единицы
иногдаexp должен содержать секунды с начала эпохи (RFC 7519). Код выпуска токенов, который неправильно делит или передаёт объект Date, сериализованный в миллисекундах, выдаёт токены, которые либо просрочены с самого рождения, либо действительны тысячелетиями.
Как исправить
- 1.
Добавьте ограниченный допуск на расхождение часов
jose принимает параметр clockTolerance (число секунд или понятную человеку строку). Делайте его небольшим: он компенсирует расхождение часов, а не продлевает срок жизни токена.
js import { jwtVerify } from "jose"; await jwtVerify(token, key, { algorithms: ["ES256"], clockTolerance: "30s", }); - 2.
Прежде чем что-то менять, выведите временную шкалу
Сравните iat, exp и текущее время проверяющей стороны. Картина подскажет причину: exp на несколько секунд в прошлом → проблема с обновлением токена; exp раньше iat → ошибка при выпуске; exp ≈ текущее время, но проверка не проходит → расхождение часов.
js import { decodeJwt } from "jose"; const { iat, exp } = decodeJwt(token); console.log({ iat: new Date(iat * 1000), exp: new Date(exp * 1000), now: new Date() });