Forging a signed badge
Watch it happen
Mallory makes her own badge claiming to be a manager and says no signature is needed. The guard accepts it.
Mallory walks up to the front door. Real badges carry a name and a role, and are pressed with the bank’s secret stamp. The guard checks that stamp.
Guard says: Badge, please.In bank terms and app terms
| In the bank | In your app |
|---|---|
Are you exposed?
Your JWT secret is short, human-readable or a placeholder like “secret” or “your-256-bit-secret”. You call jwt.decode() or an old library version without forcing the algorithm (algorithms: ["HS256"]). Tokens have no exp claim, or your server trusts role / is_admin from the token without checking elsewhere.
Verify with jwt.verify(), pass an explicit algorithms list, and reject alg: none. Use a long random secret (32+ bytes) from an env var, or let your auth provider (Supabase, Firebase, Clerk) manage tokens. Always set a short exp, and look up roles in your database instead of trusting the token’s claim.
It has happened to real companies
jsonwebtoken CVE-2015-9235 (2015) — Versions of the popular jsonwebtoken npm library before 4.2.2 could be tricked into verifying a token with an RSA public key as if it were a shared secret, letting attackers forge tokens that looked valid. Several JWT libraries from the same era also accepted alg: none, which skips signature checking entirely.
Palo Alto Networks PAN-OS CVE-2020-2021 (2020) — A SAML signature-validation flaw, when the identity provider's certificate was not being checked, let attackers bypass authentication on PAN-OS. It scored the maximum severity (CVSS 10). This one is SAML rather than JWT, but the lesson is the same: a signature you don't verify properly protects nothing.
What it looks like in code
// BAD: No algorithm restriction — the token picks its own algorithm (alg: none on old libraries, or HS256 against an RSA public key)
const payload = jwt.verify(token, SECRET)
// BAD: Weak secret
const JWT_SECRET = "secret"
const token = jwt.sign(payload, JWT_SECRET)
// BAD: No expiration
const token = jwt.sign({ userId: "123", role: "admin" }, SECRET)
// Missing: expiresIn option
Ask your AI tool to fix it
Paste this into Cursor, Claude, Lovable, Bolt or whatever you build with.
Review all JWT handling in my project. Ensure tokens are verified (not just decoded) with an explicit algorithms allow-list and never alg: none, that the signing secret is 32+ random bytes read from an environment variable, that every token has a short exp, and that authorization decisions look up the user’s role in the database instead of trusting a role claim. List every change.Where this comes from
The facts, figures and fixes on this page were checked against these sources on 25 Oct 2026. They are linked so you can check for yourself.
- RFC 7519: JSON Web Token (JWT)IETFWhat a JWT is, the exp claim, and that unsecured (alg none) tokens exist
- RFC 7518: JSON Web Algorithms (JWA)IETFHS256 needs a key of at least 256 bits (32 bytes); 'none' means no signature
- RFC 8725: JWT Best Current PracticesIETFPin the algorithm, reject none, use high-entropy secrets, do not trust the header's alg
- Critical vulnerabilities in JSON Web Token librariesAuth0alg: none and RSA-public-key-as-HMAC-secret attacks on several JWT libraries (2015)
- GHSA-c7hr-j4mj-j2w6: Verification Bypass in jsonwebtoken (CVE-2015-9235)GitHubjsonwebtoken before 4.2.2 could be tricked into verifying with a public key as a secret
- CVE-2020-2021 PAN-OS: Authentication Bypass in SAML AuthenticationPalo Alto NetworksSAML signature check flaw bypassed PAN-OS authentication when the IdP certificate was not verified
Spot a mistake or something out of date? Report a correction · How these guides are made and checked
The free, open-source scanner checks for most of the problems in these guides.