Сообщение об ошибке
Firebase\JWT\SignatureInvalidException: Signature verification failed
Также выводится как
- PHP Fatal error: Uncaught Firebase\JWT\SignatureInvalidException: Signature verification failed
Если исключение не перехвачено, PHP выводит «PHP Fatal error: Uncaught Firebase\JWT\SignatureInvalidException: Signature verification failed in …/JWT.php».
firebase/php-jwt (PHP) — Firebase\JWT\SignatureInvalidException from JWT::decode
Что это означает
php-jwt разобрал токен, принял алгоритм для переданного ключа, но проверка подписи вернула false: ключ не подходит к токену или токен изменили после подписи. `SignatureInvalidException` — подкласс `UnexpectedValueException`, поэтому общий блок catch может спрятать это исключение среди ошибок разбора.
Почему это происходит
Настроенный ключ не принадлежит подписанту
частоОткрытый ключ, скопированный из другого окружения, или ключ, который издатель уже сменил. Firebase Auth и Google подписывают ID-токены ключами, которые меняются каждые несколько дней.
Из набора ключей взят один ключ без учёта kid
частоКод, который берёт первый ключ из JWKS (например, `reset(JWK::parseKeySet($jwks))`), работает, пока издатель не начнёт подписывать другим ключом из набора.
У сервисов разные секреты HMAC
иногдаДля HS256 любое отличие в строке секрета — пробелы, завершающий перевод строки из файла, base64 вместо исходных байтов — даёт другой MAC.
Токен изменён после подписи
иногдаЗаголовок или полезная нагрузка закодированы заново, либо токен склеен из частей двух токенов.
Как исправить
- 1.
Передайте весь набор ключей, и php-jwt сам выберет ключ по kid
`JWT::decode` принимает массив объектов `Key`, ключами которого служат значения `kid` (из `JWK::parseKeySet`), или `CachedKeySet`, который к тому же обновляется при ротации.
php use Firebase\JWT\CachedKeySet; use Firebase\JWT\JWT; use GuzzleHttp\Client; use GuzzleHttp\Psr7\HttpFactory; // Keys selected by the token's kid, fetched from the issuer's JWKS and cached (PSR-6). $keys = new CachedKeySet( 'https://issuer.example.com/.well-known/jwks.json', new Client(), new HttpFactory(), $cache, // any PSR-6 cache pool 3600, // cache lifetime in seconds true // rate-limit refetches on unknown kids ); $claims = JWT::decode($token, $keys); - 2.
Сравните части пары ключей
Если открытый ключ, полученный из закрытого ключа издателя, отличается от настроенного у вас, подпись не пройдёт проверку.
bash # Do the issuer's private key and your public key belong together? openssl pkey -in issuer-private.pem -pubout | sha256sum openssl pkey -pubin -in configured-public.pem | sha256sum