Error message
Firebase\JWT\SignatureInvalidException: Signature verification failed
Also printed as
- PHP Fatal error: Uncaught Firebase\JWT\SignatureInvalidException: Signature verification failed
Uncaught, PHP prints "PHP Fatal error: Uncaught Firebase\JWT\SignatureInvalidException: Signature verification failed in …/JWT.php".
firebase/php-jwt (PHP) — Firebase\JWT\SignatureInvalidException from JWT::decode
What it means
php-jwt parsed the token, accepted the algorithm for the key you passed, and the signature check returned false: the key and the token do not belong together, or the token was changed after signing. `SignatureInvalidException` is a subclass of `UnexpectedValueException`, so a generic catch block may hide it among parsing errors.
Why it happens
The configured key is not the signer's
commonA public key copied from another environment, or a key that was rotated on the issuer side. Firebase Auth and Google sign ID tokens with keys that change every few days.
One key picked from a key set without using kid
commonCode that takes the first key of a JWKS (for example `reset(JWK::parseKeySet($jwks))`) works until the issuer signs with another key in the set.
HMAC secret differs between services
occasionalFor HS256, any difference in the secret string — whitespace, a trailing newline from a file, base64 vs raw — produces a different MAC.
The token changed after signing
occasionalHeader or payload re-encoded, or a token stitched together from parts of two tokens.
How to fix it
- 1.
Pass the whole key set and let php-jwt pick by kid
`JWT::decode` accepts an array of `Key` objects keyed by `kid` (from `JWK::parseKeySet`) or a `CachedKeySet`, which also refreshes on rotation.
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.
Compare the key halves
If the public key derived from the issuer's private key differs from the one you configured, the signature cannot verify.
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