JWT invalid signature: why verification fails

A JWT’s third part is a signature over the header and payload. The verifier recomputes it with its own key, and “invalid signature” means the two do not match. The token may be perfectly readable: decoding needs no key, verifying does. So the usual cause is a key mismatch between the service that issued the token and the one checking it. This sample decodes normally; enter a secret in the verify box and PasteKit checks the signature locally, without sending the token anywhere.

Seen as:

  • JsonWebTokenError: invalid signature
  • jwt.exceptions.InvalidSignatureError: Signature verification failed
  • JWSSignatureVerificationFailed: signature verification failed
  • io.jsonwebtoken.security.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
  • Signature does not match the supplied key

Input

Settings

History

Load from URL

Common causes

1. Issuer and verifier use different secrets

An HS256 token signed with the staging secret fails against the production secret, and a secret with a trailing space or newline from a copied .env value is a different secret too. Compare the values byte for byte.

Before
# api-service/.env (auth-service signs with production-secret-2026)
JWT_SECRET=staging-secret-2026
After
# api-service/.env (auth-service signs with production-secret-2026)
JWT_SECRET=production-secret-2026

2. A secret that is Base64-encoded on one side only

Some issuers (Auth0 legacy apps, Java’s jjwt with Base64 keys) treat the secret as Base64 and sign with the decoded bytes. Verify with the same bytes.

Before
jwt.verify(token, process.env.JWT_SECRET);
After
jwt.verify(token, Buffer.from(process.env.JWT_SECRET, 'base64'));

3. The wrong public key for RS256 or ES256

With asymmetric algorithms you verify with the issuer’s public key, and identity providers rotate keys. Select the key by the token’s kid from the provider’s JWKS endpoint instead of hard-coding one.

Before
const { payload } = await jwtVerify(token, await importSPKI(OLD_PUBLIC_KEY_PEM, 'RS256'));
After
const jwks = createRemoteJWKSet(new URL('https://auth.example.com/.well-known/jwks.json'));
const { payload } = await jwtVerify(token, jwks, { issuer: 'https://auth.example.com/' });

4. A token changed after it was signed

Editing the payload (for example on a debugging site), re-encoding it, or keeping the "Bearer " prefix changes the signed bytes. Strip the prefix and pass the token exactly as issued.

Before
const token = req.headers.authorization;
After
const token = req.headers.authorization?.replace(/^Bearer\s+/i, '');

Frequently asked questions

Can I read a JWT whose signature is invalid?

Yes. The header and payload are only Base64URL-encoded, so anyone can decode them. That is exactly why the payload must never be trusted until the signature has been verified.

Is it safe to paste a token into PasteKit to check it?

Decoding and verification run entirely in your browser and nothing is uploaded. Still, treat live tokens as credentials and prefer expired or test tokens when sharing screenshots.

Why does jwt.io say "Signature Verified" when my server rejects it?

Usually the secret or key differs between the two places: check for encoding (plain vs Base64), whitespace and environment. Also make sure the server allows the token’s algorithm.

Related