The mental model that fixes everything JWT is just a token format.
It is not authentication, not a session, and not a database.
Once you separate those ideas, most of the pain disappears.
A JWT is a JSON object that is signed.
That's it.
The payload holds claims like (subject) and (expiration).
The signature proves the token wasn't tampered with.
What JWT is not Not a session store: You can't revoke a JWT before it expires.
If you need revocation, you need a blocklist or short expiry.
Not a database: Don't stuff heavy data in the payload.
It gets sent on every request.
Not a magic bullet: It's a way to pass claims between parties without a shared server-side state.
The three flows that matter
1.
Access token only Simplest flow: login returns a JWT, client sends it in the header, server verifies it on every request.
Works fine for small apps, but every request hits your auth logic and the token can't be invalidated early.
2.
Access + refresh token Common pattern for SPAs.
Access token lives 15 minutes, refresh token lives 7 days.
The refresh token is stored securely (httpOnly cookie) and used only to get a new access token.
Refresh endpoint: This gives you short-lived access tokens (less risk if leaked) and long-lived sessions without storing server-side state.
3.
Stateless vs stateful If you need to revoke tokens immediately (like on password change), you have two options: Keep a token version in your user table.
Include in the JWT payload.
Bump the version to invalidate all old tokens.
Use a blocklist (Redis or DB) for revoked tokens.
Check the blocklist before verifying.
Both add state.
If you don't need revocation, stay stateless.
Common mistakes I see Storing JWT in localStorage: XSS can steal it.
Use httpOnly cookies for refresh tokens, and keep access tokens in memory if possible.
Putting sensitive data in payload: It's base64 encoded, not encrypted.
Anyone can read it.
Using the same secret for access and refresh: Use separate secrets.
If one leaks, the other is still safe.
Not checking : Most libraries do it automatically, but if you hand-roll, don't forget.
The decision checklist Ask these before adding JWT: Do you need to revoke tokens?
If yes, plan for a blocklist or versioning.
Are you building an API for multiple clients?
JWT works well.
Is your server the only consumer?
A simple session cookie might be easier.
Final thought JWT is a tool, not a religion.
Use it when it fits: stateless APIs, microservices, or cross-domain auth.
For classic server-rendered apps, traditional sessions are often simpler.
The confusion comes from mixing the token format with the auth strategy.
Keep them separate and you'll be fine.