Skip to content
Back to the blog

JWT vs sessions: the debate asks the wrong question

Two identical doors: the lock of the left one is wired to a register and its key is snapped in two; the right one isn't connected to anything and its key is still whole

For years, the authentication debate has been stuck on where to store the token, which is really the second question. The first is who decides when a session ends. If the answer is “the token itself”, you can’t kick anyone out until it expires, and that changes the whole design.

The debate that keeps coming back

Look up how to handle authentication in your product and you’ll always find the same argument: localStorage versus an HttpOnly cookie. That question was settled years ago, so it can be dealt with quickly:

  • localStorage can be read by any JavaScript on the page. If a script gets in through a compromised dependency or an XSS flaw, the token can be read and stolen. OWASP explicitly advises against storing session identifiers there.
  • An HttpOnly cookie can’t be read by JavaScript, yours or the attacker’s.
  • Current practice is a short-lived access token in memory and a refresh token in an HttpOnly, Secure, SameSite cookie.

Every guide says as much. The trouble is that settling it leaves the important question untouched.

The question almost nobody asks

A JWT is a piece of signed data: the server validates it by checking the signature, without looking anything up. That’s its whole appeal, since nothing needs to be queried, and it’s exactly where its problem comes from.

If you don’t look anything up, you can’t know that the token is no longer valid.

Four ordinary situations, the kind that happen every week in a product with customers:

SituationWith a server-side sessionWith a self-contained JWT
An employee leaves and their access is revokedThey’re locked out immediatelyThey keep getting in until it expires
Someone’s admin role is taken awayTakes effect immediatelyThey keep admin permissions until it expires
A customer logs out on someone else’s computerThe session is destroyedThe token is still valid
You suspect an account has been compromisedYou end it and you’re doneThere’s no way to end it

The right-hand column describes a JWT used exactly as its documentation suggests. Nothing in it has been implemented badly.

The middle box is the whole difference: if nothing gets checked, there is no way to know that access no longer exists.

The workarounds, and what each one costs

Everyone runs into this sooner or later and reaches for one of three workarounds. Each comes at a price.

Very short expiry. Five-minute tokens and constant refreshing. The exposure window shrinks without disappearing, and refresh requests multiply. On top of that, the refresh token does have to be revocable, so you need state on the server again: you’ve just reinvented the session, with two pieces instead of one.

A revocation list. You store the IDs of invalidated tokens and check them on every request. It works, but you’ve just given up the JWT’s only advantage: you’re already querying a store on every request, just as a session does, only with more complexity.

Credential versioning. A counter on the user that goes up when the password or permissions change and travels inside the token. It’s the cleanest workaround, but it also has to be read, so once again you’re querying.

The pattern repeats: every fix for the revocation problem takes you back to server-side state, which is what the JWT was meant to avoid.

So what should you use?

The rule Nimboo follows, which makes no claim to originality:

Server-side sessions for your own users. JWTs for whatever crosses a boundary.

A server-side session, meaning an opaque ID in an HttpOnly cookie with the data in your own store, is the right choice for your product’s web and mobile apps. It can be revoked instantly, you can list “signed-in devices”, logging out actually logs out, and the cost is one read per request from a store you already have.

A JWT fits where whoever validates it can’t ask whoever issued it: communication between separate services, third-party integrations, signed single-use links, or identity federation. There, the signature solves a real problem that a session can’t.

The confusion comes from the JWT being popularized as “the modern way to do login”, when the problem it solves well is a different one.

What’s still wrong even if you use sessions

Switching from tokens to sessions doesn’t fix what actually breaks in audits:

  • Logging out doesn’t invalidate the session on the server. Deleting the cookie in the browser leaves the ID valid for anyone who had it.
  • The session isn’t renewed when privileges are elevated. On login or a role change a new ID has to be issued, or an attacker who planted the old one keeps it (session fixation).
  • No inactivity timeout. A session that lasts forever is a problem the moment someone leaves a laptop open.
  • The second factor stops at the login screen. If it’s only checked when signing in and the session then lasts for days, the protection is weaker than it looks.
  • SameSite missing or misconfigured. Without it, a request from another site carries the cookie along, and that’s where CSRF comes in.

When none of this matters

If your product is an internal tool with a stable set of users and no third-party data, a one-hour JWT with no revocation is perfectly reasonable and saves you work. The real risk is low and the simplicity is worth it.

The threshold is one concrete question: could anyone need access revoked before the token expires? As soon as you sell to businesses, manage your client’s employees or touch personal data, the answer is yes, and a self-contained JWT stops being good enough.

And a recommendation that saves arguments: start with server-side sessions. They’re easier to reason about, they can be revoked, and if you ever need signed tokens to talk to another system, they go on top without redoing anything. The opposite route, from JWT to sessions, means touching the client, the server and the data model all at once.


Vecinly manages homeowners’ associations with fine-grained roles, where someone stops being board president and their access has to be cut off that same day. It’s in its case study. If you have a prototype with authentication cobbled together, custom SaaS starts by reviewing exactly this.

← Back to the blog Tell us about your project →