JWT verify errors — expired, malformed, invalid signature
Quick answer: the jsonwebtoken library throws a different error per cause: jwt expired means the exp claim has passed (issue a fresh token), jwt malformed means the value is not a valid 3-part token (often the Bearer prefix or undefined), and invalid signature means the secret or key does not match what signed it. Fix each by its cause - they are not the same bug.
What the errors look like
Each is a distinct error name from jwt.verify() - the name tells you the cause:
TokenExpiredError: jwt expired
JsonWebTokenError: jwt malformed
JsonWebTokenError: invalid signature
JsonWebTokenError: invalid algorithm
Do not treat them as one generic “auth failed.” expired is a valid token past its time; malformed is not a token at all; invalid signature is a real token signed by a different key.
Why each one happens
jwt expired
The exp claim has passed - the lifetime was short, or server clocks are skewed.
jwt malformed
The value is not a 3-part token - usually the 'Bearer ' prefix was not stripped, or it is undefined.
invalid signature
Verified with a different secret/key than signed it - an env mismatch across instances, or a rotated key.
invalid algorithm
Signing and verifying disagree on the algorithm - e.g. HS256 vs RS256.
Diagnose it in three steps
Decode (do not verify) to inspect
const jwt = require('jsonwebtoken');
console.log(jwt.decode(token, { complete: true }));
// read exp, alg, iss - or paste it into a JWT inspector.Confirm you stripped 'Bearer '
const raw = req.headers.authorization || '';
const token = raw.replace(/^Bearer /, ''); // a top 'malformed' cause
console.log(JSON.stringify(token));Check the secret and algorithm match
# same JWT_SECRET on the signing and verifying side?
# same algorithm? pin it on verify so a mismatch is explicit.Strip Bearer, pin the algorithm, match the secret
Verify the raw token (no prefix) with the same secret and a pinned algorithm, and handle expiry as its own case so the client can refresh instead of being logged out hard.
const jwt = require('jsonwebtoken');
const token = (req.headers.authorization || '').replace(/^Bearer /, '');
try {
const payload = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'], // pin it - never accept 'none'
clockTolerance: 5, // seconds, for minor clock skew
});
req.user = payload;
} catch (e) {
if (e.name === 'TokenExpiredError') return res.status(401).json({ error: 'expired' });
return res.status(401).json({ error: 'invalid token' });
}
If you see invalid signature only on some requests, you almost certainly have a different JWT_SECRET on different instances - make the secret identical everywhere it is verified.
One consistent secret across your fleet
Intermittent invalid signature failures are a config-consistency problem: one instance signs with a secret another does not share. On your own servers, Infraveil distributes and manages config and secrets consistently across the services you run, so every instance verifies with the same key - and changes are approval-gated and recorded.
Frequently asked questions
What does 'jwt expired' mean?
The token's exp claim is in the past, so verify throws TokenExpiredError. Issue a fresh token (via a refresh token) rather than extending lifetimes; a small clockTolerance absorbs minor clock skew.
What causes 'jwt malformed'?
The value passed to verify is not a valid three-part JWT - usually the 'Bearer ' prefix was not stripped, or it is undefined/empty. Log the exact value you verify.
What causes 'invalid signature'?
Verifying with a different secret or key (or algorithm) than signed the token - often a different JWT_SECRET on another instance, a rotated key, or HS256 vs RS256. Match both sides exactly.
How do I inspect a token safely?
Decode rather than verify - the header and payload are base64url JSON you can read without the secret. Use a JWT inspector to check exp/alg/claims, but never trust an unverified token for access.
Client-side, no signup — they run in your browser.