エラーメッセージ
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 を使わずに鍵セットから 1 つの鍵を選んでいる
よくあるJWKS の最初の鍵を取り出すコード(たとえば `reset(JWK::parseKeySet($jwks))`)は、発行者が鍵セット内の別の鍵で署名するまでは動作します。
サービス間で HMAC シークレットが異なる
ときどきHS256 では、シークレット文字列に少しでも違い(空白、ファイル由来の末尾の改行、base64 か生の値か)があると、異なる MAC が生成されます。
署名後にトークンが変更された
ときどきヘッダーやペイロードがエンコードし直された場合や、2 つのトークンの一部をつなぎ合わせてトークンが作られた場合が該当します。
修正方法
- 1.
鍵セット全体を渡し、kid による選択を php-jwt に任せる
`JWT::decode` には、`kid` をキーとする `Key` オブジェクトの配列(`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