JWT OAuth architecture mistakes
Common pitfalls in JWT-based authentication — signature validation, payload storage, revocation, and token lifetime — and how to avoid them.

JWTs are popular for stateless authentication, but their simplicity hides architectural traps. A JWT is just a signed token; the moment you misunderstand what "signed" and "stateless" mean, you create real vulnerabilities. This article breaks down the most common mistakes and the patterns that avoid them.
1. Not validating the signature (and algorithm)
A JWT has three parts: header, payload, signature. The signature proves the issuer created it — but only if you verify it with the correct secret/key and algorithm.
ts
// Dangerous: trusting the token without verifying
const payload = JSON.parse(atob(token.split(".")[1]));
// Correct: verify with the library
const verified = jwt.verify(token, publicKey, { algorithms: ["RS256"] });
A classic attack is algorithm confusion: an attacker sends a token signed with HS256 using the public key as the HMAC secret. Pin algorithms to prevent it.
2. Putting secrets in the payload
The payload is base64-encoded, not encrypted. Anyone can read it.
- Never store passwords, tokens, or PII in a JWT.
- Treat the payload as public data the server asserts about the user.
If you need confidentiality, encrypt the token (JWE) or keep sensitive data server-side.
3. No revocation strategy
JWTs are stateless, so they're valid until they expire — even after a user logs out or is compromised. Options:
- **Short access tokens** (minutes) + **refresh tokens** you can revoke.
- A **denylist** or **version/jti** check for revoked tokens.
- Rotate refresh tokens and store them server-side.
ts
// Refresh token stored and checked on logout/compromise
const session = await db.sessions.find(refreshTokenId);
if (!session || session.revokedAt) return res.status(401).end();
4. Wrong token lifetimes
- Long-lived access tokens = big blast radius if leaked.
- Refresh tokens that never expire = permanent sessions.
Use short access tokens (5–15 min) and refresh tokens with rotation + revocation.
5. Unsafe client storage
- **localStorage** — vulnerable to XSS token theft.
- **httpOnly cookies** — not readable by JS, much safer for web.
http
Set-Cookie: refresh=...; HttpOnly; Secure; SameSite=Strict; Path=/auth
Pair with CSRF protection when using cookies.
6. Letting the API surface grow
Keep auth at the edge: a gateway or middleware validates the token once, then passes a trusted identity downstream. Don't re-verify in every service ad hoc.
Mistakes summary
| Mistake | Fix |
|---|---|
| No signature check | Verify with pinned algorithm |
| Secrets in payload | Keep payload public-only |
| No revocation | Short access + revocable refresh |
| Long-lived tokens | Rotate and expire |
| localStorage JWT | httpOnly, Secure cookies |
JWTs are fine — the mistakes are architectural. Validate strictly, keep tokens short-lived, store them safely, and always have a way to revoke.