Error message
Signature verification failed
PyJWT (Python) — jwt.exceptions.InvalidSignatureError
What it means
PyJWT decoded the token's structure and ran the signature check, which failed: the signature does not match the key and payload. InvalidSignatureError subclasses DecodeError, so an over-broad except DecodeError can mislabel this as a parsing problem — it is a key/content mismatch. For ES256 tokens there is one extra subtlety: the JWS signature must be the raw 64-byte r‖s form, so tokens minted by hand-rolled signers that emitted ASN.1 DER fail here even when the key is right.
Why it happens
Different key than the issuer used
commonA rotated key pair where the verifier kept the old public key, a copy-paste of the wrong PEM, or two services reading different secret versions from the secret manager. With JWKS-based issuers, a kid mismatch has the same effect.
PEM damaged by config plumbing
commonKeys stored in .env files or YAML frequently lose their newlines (literal \n) or gain quotes/indentation. cryptography may still load a superficially valid PEM that is not the right key — and every verification fails.
Token altered after signing
occasionalMiddleware that re-encodes the payload, base64 padding normalization, or truncation in a database column changes the signed bytes. The signature is over the exact base64url text — cosmetic changes are not cosmetic.
How to fix it
- 1.
Normalize the key and confirm its identity
Restore real newlines if the PEM came through an environment variable, and print the first line plus a fingerprint so both sides can compare keys.
python import os pem = os.environ["JWT_PUBLIC_KEY"].replace("\\n", "\n").encode() print(pem.splitlines()[0]) # b'-----BEGIN PUBLIC KEY-----' import jwt payload = jwt.decode(token, pem, algorithms=["ES256"]) - 2.
Check kid before assuming corruption
Read the unverified header and make sure you are verifying with the key the token names. With multiple issuer keys, resolve by kid via PyJWKClient.
python import jwt header = jwt.get_unverified_header(token) print(header) # {'alg': 'ES256', 'kid': '...'} from jwt import PyJWKClient key = PyJWKClient("https://issuer.example/.well-known/jwks.json") \ .get_signing_key_from_jwt(token).key payload = jwt.decode(token, key, algorithms=["ES256"], audience="api://orders")