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
apps/web/app/api/route.ts:22JWT verification
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.
// 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.verifyChecklist
- 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.
Related guides
If your webhook endpoint skips signature verification, an attacker can trigger any downstream action you code — refunds, subscription changes, user upgrades. Here is how to verify signatures correctly for the five most common webhook providers.
Misconfigured cookies are how session tokens leak. Here is exactly which flags to set for session, CSRF, and preference cookies in a Next.js app.
Most OAuth bugs come from skipping PKCE, ignoring state, or accepting tokens issued for a different client. Here is the correct implementation in a Next.js + NextAuth app.
A logged-in user is a trust decision you made at login. Sessions need explicit policies for idle timeout, absolute timeout, revocation, and rotation.