Ffile2fix
Sign in Get started

Fix JWT "invalid signature"

JsonWebTokenError: invalid signature

The token's header and payload decode fine, but the signature does not match when recomputed with your verification key. The token was signed with a different secret or private key, with a different algorithm, or was altered after signing.

Also appears as: Firebase\JWT\SignatureInvalidException: Signature verification failed · jwt.exceptions.InvalidSignatureError: Signature verification failed · JWSSignatureVerificationFailed: signature verification failed · IDX10503: Signature validation failed.

Common causes

  • The signing and verifying services use different secrets, for example different .env values per environment
  • The secret has a trailing newline, quotes or spaces from the environment file
  • The secret is base64-encoded in one place and used as raw text in the other
  • An RS256/ES256 token is verified with the wrong public key or a rotated key (kid mismatch in JWKS)
  • The token was copied with extra characters or edited, so its bytes no longer match the signature
  • The token was issued by a different identity provider, tenant or project than the one you verify against

How to fix it

  1. Decode the header and payload. Decode the token without verifying and look at alg, kid and iss. They tell you which algorithm and key should be used and who issued the token.
  2. Compare secrets exactly. Print the length and a hash of the secret in both services, for example node -e "console.log(require('crypto').createHash('sha256').update(process.env.JWT_SECRET).digest('hex'))". Different hashes mean different secrets.
  3. Check encoding. If the secret was generated as base64, decode it to bytes in both places (Buffer.from(secret, 'base64') in Node) or use it as text in both places, but not mixed.
  4. Use the right key for asymmetric algorithms. For RS256 or ES256, verify with the issuer's public key in PEM format or fetch it from the JWKS endpoint by kid. Refresh cached keys after the provider rotates them.
  5. Pin the algorithm. Pass an explicit allow-list such as algorithms: ['HS256'] when verifying. This prevents algorithm confusion attacks and makes mismatches fail with a clear error.
  6. Issue a fresh token. After changing secrets, existing tokens signed with the old key will keep failing. Log in again or refresh the token to get one signed with the current key.

Node.js (jsonwebtoken)

const jwt = require('jsonwebtoken');
const secret = process.env.JWT_SECRET.trim();

const token = jwt.sign({ sub: '42' }, secret, { algorithm: 'HS256', expiresIn: '1h' });
const payload = jwt.verify(token, secret, { algorithms: ['HS256'] });

How to stop it happening again

  • Load the signing secret from one shared secret store, not hand-copied .env files
  • Always pin accepted algorithms when verifying
  • Use kid and JWKS with key rotation for asymmetric keys
  • Validate environment files for missing or malformed variables during deploy

Frequently asked questions

Can I read the payload if the signature is invalid?

Yes, the header and payload are only base64url-encoded, so anyone can decode them. Never trust that data, though, until the signature verifies.

Why does the token verify on jwt.io but not in my app?

You probably pasted a different secret into jwt.io than the app loads, or the app adds a newline or uses base64 decoding. Compare the exact bytes your app uses.

Is "invalid signature" the same as an expired token?

No. Expiry gives a separate error such as TokenExpiredError or ExpiredException. Invalid signature means the key or the token content does not match.