JWT verification — the five ways apps get it wrong
JWTs are only as secure as your verification. Missing issuer check, missing expiration check, `alg: 'none'`, and algorithm confusion all still ship in AI-generated code.
A JWT is just a signed envelope. Every property that makes it secure comes from your verification. This guide covers the five verification bugs we see most, in order of frequency.
What it is
A JSON Web Token carries a payload and a signature. Verification checks that the signature matches the payload under an expected algorithm and key, and that the declared claims (issuer, audience, expiration) match what your app expects.
Vulnerable example
// Vulnerable: decode without verify
import jwt from "jsonwebtoken";
const payload = jwt.decode(token) as { sub: string };
// Attacker forges ANY payload. jwt.decode does not verify signatures.
// Vulnerable: verify without checking alg
jwt.verify(token, publicKey);
// Vulnerable to alg: 'none' and HS256/RS256 confusion attacks.Fixed example
// Fixed: verify with explicit algorithm and claim checks
import jwt from "jsonwebtoken";
const payload = jwt.verify(token, publicKey, {
algorithms: ["RS256"], // pin the algorithm
issuer: "https://yourapp.com", // check issuer
audience: "api.yourapp.com", // check audience
});
// expiration is checked by default by jwt.verifyHow Securie catches it
Securie's auth specialist traces every jwt.verify and jwt.decode call, requiring explicit algorithms, issuer and audience, and flagging any `decode`-without-verify usage.
Checklist
- Algorithms whitelist is explicit (never default or empty)
- Issuer claim is verified
- Audience claim is verified
- Expiration is checked (default in most libs — do not disable)
- JWT is rejected if the key is not a known JWK ID
- Public keys rotated at least annually
FAQ
What about `jsonwebtoken` vs `jose`?
jose is more modern and handles JWS + JWE + JWK well. Most new apps should use it. Either is fine if verification options are explicit.