JWT errors and how to fix them

Most JWT errors come from a handful of mistakes, and the error text tells you which one. Messages below are from Node's jsonwebtoken; the equivalents in PyJWT, jose and Java libraries are listed next to them. To look inside a token while you debug, paste it into the JWT decoder, which runs in your browser.

On this page
  1. jwt malformed / Not enough segments
  2. invalid signature / Signature verification failed
  3. jwt expired / Signature has expired
  4. jwt not active
  5. invalid algorithm
  6. jwt audience invalid / jwt issuer invalid
  7. secret or public key must be provided
  8. No authorization token was found

jwt malformed

PyJWT: DecodeError: Not enough segments. jose: JWSInvalid: Invalid Compact JWS.

What reached the verify call isn't header.payload.signature. Log exactly what you pass. The usual causes:

invalid signature

PyJWT: InvalidSignatureError: Signature verification failed. jose: JWSSignatureVerificationFailed.

The token is well-formed, but the key you verify with isn't the key it was signed with.

To check a secret directly, paste the token and the HS256 secret into the decoder's signature check.

jwt expired

TokenExpiredError: jwt expired. PyJWT: ExpiredSignatureError: Signature has expired. jose: JWTExpired.

The exp time has passed. The normal fix is on the client: get a new access token with the refresh token and retry. If tokens are expired as soon as they're issued:

jwt not active

NotBeforeError: jwt not active. PyJWT: ImmatureSignatureError. The token has an nbf (not before) time in the future, almost always because the issuing server's clock is ahead of the verifying one. Sync clocks with NTP, or allow a few seconds of tolerance.

invalid algorithm

The token's header says one algorithm (say HS256) and your algorithms list allows another (say RS256). Pin the list to what your issuer uses, and never accept none, or let the token choose between an HMAC secret and a public key; that mix-up is a well-known forgery technique.

jwt audience invalid / jwt issuer invalid

The aud or iss claim doesn't match what you asked the library to require. Decode the token to see the real values. Common causes: a token requested for a different API (wrong audience parameter at login), a trailing slash in the issuer URL (https://auth.example.com/ vs https://auth.example.com), or aud being an array.

secret or public key must be provided

The key variable is undefined: the environment variable isn't loaded in this process. Check dotenv is loaded before the module that reads it, and that the variable is set in your hosting provider, not only locally.

No authorization token was found

From express-jwt and similar middleware: no Authorization: Bearer … header arrived. Check the request in DevTools' Network tab. If the header is missing there, the client didn't send it. If it's there on the OPTIONS request's follow-up but the error comes from the OPTIONS request itself, your middleware is running on the CORS preflight, which never carries it.

Testing with a different token

The JWT generator signs HS256 test tokens with any payload, including expired ones or ones with the wrong audience, so you can check each error path. To send a token from your browser without changing the app:

HeaderForge (Chrome, free)

Sets Authorization: Bearer … on requests to the sites you choose, keeps the token in a variable so it stays out of exported rules, and shows whether the rule fired. No account, no tracking.

Get HeaderForge for Chrome

More on this topic: UUID generator · How to add a custom HTTP header to requests in Chrome · HTTP headers list: the request and response headers that matter · Referer header: what it sends, why it's empty, and how to change it