Сообщение об ошибке
invalid signature
Что это означает
Токен разобран без ошибок, и его структура корректна, но криптографическая проверка не прошла: подпись заголовка и полезной нагрузки (header.payload) не проходит проверку переданным вами ключом. Либо это не тот ключ, которым подписан токен, либо подписанные байты изменились после подписания.
Почему это происходит
Проверка не тем ключом
частоБезусловно самый частый случай: в окружении лежит не тот ключ, которым пользовался издатель — устаревший открытый ключ после ротации, ключ staging-окружения для продакшен-токенов или несовпадение kid в JWKS, когда проверяющая сторона жёстко задаёт один ключ, а издатель подписывает другим.
Самописная реализация подписи подписала не те байты
иногдаКод, который собирает токены вручную, должен подписывать ASCII-байты base64url(header).base64url(payload) с SHA-256 и выдавать значение r‖s в формате raw длиной 64 байта, как требует RFC 7518 для ES256. Если подписать сам JSON, сегменты в стандартном base64 или дайджест SHA-384 под заголовком ES256, получится корректно сформированная подпись, которая никогда не пройдёт проверку. (Подпись в кодировке DER отклоняется ещё до проверки с сообщением «"ES256" signatures must be "64" bytes», а PEM, испорченный при передаче через переменную окружения, вообще не принимается как ключ: ошибка будет «secretOrPublicKey must be an asymmetric key when using ES256».)
Токен изменили после подписания
иногдаПрокси, логгер или клиент заново сериализовал JSON полезной нагрузки или изменил в нём символы. Даже один изменённый символ в заголовке или полезной нагрузке делает подпись недействительной — в этом и смысл подписи. (Дополнение Base64, добавленное к сегменту, приводит к ошибке шагом раньше, с сообщением «invalid token»; обрезанный токен даёт «jwt malformed» или ошибку длины подписи.)
Как исправить
- 1.
Сравните отпечатки ключа на обеих сторонах
Вычислите хеш открытого ключа, который на самом деле загрузила проверяющая сторона, и сравните его с хешем ключа издателя. Разные отпечатки означают устаревший или не тот ключ (безусловно самую частую причину) и завершают поиск за один шаг.
js import { createHash, createPublicKey } from "node:crypto"; // Run on the verifier, and on the issuer with its signing key: the outputs must match. const spki = createPublicKey(process.env.JWT_PUBLIC_KEY).export({ type: "spki", format: "der" }); console.log(createHash("sha256").update(spki).digest("hex")); - 2.
Сопоставьте kid токена с ключом проверки
Декодируйте заголовок без проверки и сравните его kid с ключом, который есть у вас. Если издатель публикует JWKS, выбирайте ключ по kid, а не прописывайте его в коде.
js const { header } = jwt.decode(token, { complete: true }); console.log(header.kid, header.alg); // compare with your JWKS / configured key - 3.
Пусть отладчик назовёт расхождение
Вставьте токен и открытый ключ в отладчик JWT: он локально проверяет ES256/ES384/ES512 и не ограничивается ответом «да/нет», а различает неверный ключ, подменённую полезную нагрузку и некорректную кодировку подписи.