JWT Expired / Invalid Token
The JSON Web Token sent with the request is expired, malformed or signed with a different key, so the API rejects it (usually with 401).
Meaning
JWTs carry an exp (expiry) claim; once the current time passes it, every API that validates the token rejects it. “Invalid token” variants mean the signature doesn’t verify (wrong secret/key, wrong algorithm), the token was truncated, or claims like aud/iss don’t match.
Common causes
- Access token passed its
exptime and wasn’t refreshed - Server clock skew (token looks expired or “not before”
nbf) - Token signed with a different secret/key than the verifier uses (e.g. different environments)
- Algorithm mismatch (HS256 vs RS256) or rotated keys/JWKS cache
- Token truncated or with extra quotes/whitespace (copied from logs)
aud/issclaims don’t match what the API expects
⚡ Quick fix
- Decode the token (header + payload) and check
exp,iss,aud,alg - Implement refresh: on 401, use the refresh token to get a new access token, then retry once
- Sync server clocks (NTP) and allow a small leeway (30–60s)
- Make sure issuer and verifier share the same secret/public key
Detailed fix by platform
Node.js
- jsonwebtoken with clock tolerance and explicit algorithms:javascript
import jwt from 'jsonwebtoken'; try { const claims = jwt.verify(token, publicKey, { algorithms: ['RS256'], audience: 'my-api', clockTolerance: 30 }); } catch (err) { if (err.name === 'TokenExpiredError') return res.status(401).json({ error: 'token_expired' }); return res.status(401).json({ error: 'invalid_token' }); }
PHP
- firebase/php-jwt: set
JWT::$leeway = 30;and pass the correctnew Key($key, 'RS256'). CatchExpiredExceptionseparately to trigger refresh.
Python
- PyJWT:
jwt.decode(token, key, algorithms=["RS256"], audience="my-api", leeway=30); handlejwt.ExpiredSignatureError.
Code examples
Decode a JWT payload locally (no verification)
javascript
const payload = JSON.parse(atob(token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/')));
console.log(new Date(payload.exp * 1000), payload.aud, payload.iss);Never trust a decoded-but-unverified token on the server — this is only for debugging.
How to diagnose
- Token present — Is the full token sent as
Bearer <token>? - Claims — exp / nbf / iss / aud correct?
- Signature — Same key and algorithm on issuer and verifier?
- Clock — Are server clocks in sync?
- Refresh — Does the client refresh before/after expiry?
🧠 Still stuck? Analyze your error
Paste the full message, response headers or stack trace — we'll detect the platform and point to the most likely cause.
Was this page helpful?
Report a correction or suggest an improvement
Last updated 2 Oct 2026