JWTs are popular because they're stateless and self-contained, but several well-documented implementation mistakes have led to real-world authentication bypasses across many frameworks and libraries.
Algorithm confusion attacks
Some JWT libraries historically allowed the alg header in the token itself to dictate which algorithm is used for verification. An attacker can craft a token specifying alg: none, or switch from an asymmetric algorithm (RS256) to a symmetric one (HS256) using the public key as the HMAC secret — tricking a naive verifier into accepting a forged token. Always explicitly specify the expected algorithm when verifying, rather than trusting the token's own header.
JWTs are not encrypted by default
A standard JWT (JWS) is base64-encoded and signed, not encrypted — its payload is fully readable by anyone who intercepts it, they just can't modify it without invalidating the signature. Don't put sensitive data (passwords, full PII, secrets) in a JWT payload assuming it's protected from viewing.
Missing or excessive expiration
A JWT without an exp claim, or with an unreasonably long one, remains valid indefinitely if stolen. Set short, reasonable expiration times, and pair short-lived access tokens with a separate refresh mechanism for longer sessions.
Revocation is genuinely hard
Because JWTs are stateless and self-verifying, there's no built-in way to revoke one before it expires — the whole point of the design is avoiding a server-side lookup. If you need real-time revocation (e.g. for a compromised account), you need an additional server-side denylist or short expiration combined with frequent refresh checks, which reintroduces some of the statefulness JWTs were designed to avoid.
Signature verification, always
It sounds obvious, but skipping or improperly implementing signature verification (trusting the payload without confirming the signature is valid) is a recurring real-world vulnerability class — always verify the signature using a known, trusted key before trusting any claim in the payload.
Practical guidance
Use a well-maintained, actively supported JWT library for your language rather than implementing verification manually, explicitly pin the expected algorithm, keep expiration times short, and never store sensitive data unencrypted in the payload.