Tech · 6 min read
JWT Explained: When to Use Them, When to Run, and What People Get Wrong
JSON Web Tokens are useful, popular, and routinely misused. A practical guide to what JWTs actually solve, where they fail, and the patterns that keep auth simple and safe.
By Jarviix Engineering · Apr 19, 2026
JSON Web Tokens are everywhere — and routinely misused. Half the security tickets in any auth-heavy codebase trace back to a JWT decision someone made on autopilot.
This post is a calm guide to what JWTs actually are, what they're good at, where they fail, and the patterns that keep auth simple and safe.
What a JWT actually is
A JWT is a string in three base64url-encoded parts, separated by dots:
header.payload.signature
Each part is just JSON until it's encoded:
// header
{ "alg": "RS256", "typ": "JWT" }
// payload (claims)
{ "sub": "user_42", "exp": 1750000000, "scope": "read:users" }
// signature
HMACSHA256( base64(header) + "." + base64(payload), secret )
The signature lets anyone with the public key (or shared secret) verify two things: the token wasn't tampered with, and it was issued by someone who knows the signing key.
That's it. That's the whole technology. Everything else — sessions, auth flows, OAuth — are usage patterns built on top.
What JWTs are genuinely good at
Three use cases where JWTs earn their keep:
- Stateless service-to-service auth. Service A calls Service B with a JWT signed by an identity provider. Service B verifies the signature with the IdP's public key — no DB lookup, no shared cache, no network round trip to validate.
- Federated trust. Your IdP issues a JWT. Your APIs, your partner APIs, and your CDN all verify it with the same public key. No one needs to share a session store.
- Short-lived bearer credentials. Pre-signed download URLs, magic links, short-lived API tokens, password-reset tokens. The "no DB lookup" property + a 5-minute expiry is genuinely useful.
For these, JWTs are the right tool.
Where JWTs go wrong
The misuse is usually one of these:
Using JWTs for browser sessions
This is the big one. Every "let's use JWTs for our login system" project eventually meets the following problems:
- No server-side revocation. A logged-out token is still valid until expiry. To "log out everywhere", you build the deny-list you were trying to avoid.
- Stale claims. If
rolesis in the JWT and the user's role changes, the old token still has the old role until expiry. - Storage in the browser is awkward. localStorage is XSS-readable; cookies (the right answer) defeat the "stateless" pitch because you may as well store a session ID.
For 95% of web apps, server-side sessions with a session ID in an httpOnly cookie are simpler, safer, and more flexible. Redis is fast. Sessions are easy to revoke. State is fine.
JWTs win when there's a cross-service or cross-domain validation problem. For one app talking to one server, sessions are the boring correct answer.
Trusting alg: none
The original JWT spec allows "alg": "none", meaning "no signature, just trust the payload". Several JWT libraries used to accept tokens with alg: none by default. Several teams had every account on their platform compromised when an attacker dropped the signature and edited claims.
Always pin the algorithm at verification time. Don't trust the header.
jwt.decode(token, key=PUBLIC_KEY, algorithms=["RS256"]) # NOT [token's claimed alg]
Confusing symmetric and asymmetric keys
HS256 uses a shared secret — anyone who can verify the token can also forge one. RS256 (and ES256) use a private/public key pair — only the issuer can sign, anyone with the public key can verify.
For multi-service systems, use asymmetric. Otherwise you're shipping the signing key to every service that needs to verify, multiplying the attack surface.
Putting too much in the payload
JWTs are signed but not encrypted. The payload is base64-decoded by anyone, including the user. So:
- Don't put PII you wouldn't want them to see.
- Don't put secrets.
- Keep the payload small — it travels on every request.
A good claim set is: subject (user ID), expiry, audience, the minimum scope information needed to authorize the request. Anything else, look up server-side.
Not setting expiry, audience, or issuer
A JWT without exp lives forever. A JWT without aud (audience) or iss (issuer) can be replayed against unintended services.
{
"iss": "https://auth.jarviix.com",
"aud": "https://api.jarviix.com",
"sub": "user_42",
"exp": 1750000000,
"iat": 1749996400,
"jti": "tok_abc"
}
Verifiers should validate all four: signature, expiry, audience, issuer. Skipping any of them turns the JWT from "auth credential" into "static text someone can paste anywhere".
The standard pattern: short access + long refresh
The pattern most production systems converge on:
- Access token. A JWT, 5–15 minutes lifetime. Stateless. Verified at every API call.
- Refresh token. An opaque random string (not a JWT). Long-lived (days/weeks). Stored server-side. Used to mint new access tokens.
Logging out → invalidate the refresh token in your store. The access token expires shortly. Worst case: the user has 5 minutes of action left after logout, which is acceptable for most products.
For high-sensitivity actions (password change, email change, money movement), require re-authentication regardless of token validity.
Refresh token rotation
A small but high-value upgrade: rotate the refresh token every time it's used.
Client uses refresh token R1 → server returns new access token + new refresh token R2.
R1 is now invalidated. Future use of R1 → forced logout of all sessions for that user.
If an attacker steals R1 and uses it once, the next time the legitimate client tries to use it, R1 is already burnt → the user is forced to log in again, and you've detected the theft.
This pattern (called "refresh token rotation with reuse detection") is now an OAuth best practice. Every modern OIDC library supports it.
A small checklist
Before shipping a JWT-based system, verify all of these:
- Algorithm is pinned at verify time (not read from the token header).
- Asymmetric signing if more than one service verifies tokens.
-
exp,iss,aud,nbf(not-before) all checked. - No PII or secrets in the payload.
- Tokens stored in httpOnly secure cookies, not localStorage.
- Short access lifetime (≤ 15 min for sensitive APIs).
- Refresh token rotation enabled.
- Server-side deny-list for important revocations (logout-everywhere, account suspension).
- Clock skew tolerance configured (a few seconds, not minutes).
- Library version pinned and current — JWT library CVEs are real.
What to read next
JWTs are one piece of a larger puzzle. The backend security checklist covers the other pieces — secrets, OWASP top 10, transport security — that decide whether your auth layer is genuinely safe or just feels safe.
Frequently asked questions
Should I use JWTs for session cookies?
Usually no. Server-side sessions in Redis are simpler, support instant revocation, and avoid every JWT pitfall. Reach for JWTs when you genuinely need stateless verification across services or third parties.
Where should I store JWTs in a browser?
Httpoonly, secure, sameSite cookies. Not localStorage — it's accessible to any XSS that lands on the page.
Are short expiries enough for security?
Short expiries help but don't replace revocation. If a token leaks at 0:00, even a 5-minute window is enough to do real damage. Short expiries + a deny-list + rotated refresh tokens is the realistic posture.
Read next
Apr 19, 2026 · 7 min read
OAuth 2.0 and JWT: Authentication Patterns Every Backend Engineer Should Know
OAuth 2.0 flows, JWT structure, when to use each, and the security pitfalls that compromise most implementations.
Apr 19, 2026 · 6 min read
A Backend Security Checklist for Working Engineers
The non-negotiable security practices every backend engineer should ship by default — auth, input validation, secrets, transport, dependencies, and the small habits that keep systems out of breach reports.
Apr 19, 2026 · 6 min read
API Versioning Strategies: URL, Header, and the Trade-offs Nobody Tells You
URL versioning, header versioning, content negotiation, and 'no versioning at all' — what each costs, what each gets you, and how to pick a strategy you won't regret in three years.