toolhq.io

All posts
5 min readby Jameel Haider

JWT vs session cookies

Two apps both keep a user logged in, but one stores a long encoded token in the browser and the other stores a short random string. The first is using JWTs, the second server side sessions. They solve the same problem in opposite ways, and the right choice usually comes down to one question: how quickly do you need to revoke access.

How each one works

A server side session stores the user's state on the server, in memory, a database, or a cache. The browser holds only an opaque session ID inside a cookie. On every request the server looks up that ID to find who the user is.

Cookie: sid=9f8a7b...   ->   server lookup   ->   { user: 1042, role: admin }

A JWT moves that state into the token itself. The payload carries the user ID, roles, and expiry, and a signature proves the server issued it. The server does not store anything; it verifies the signature and reads the claims directly.

Authorization: Bearer eyJ...   ->   verify signature   ->   trust the claims

The core tradeoff: revocation

This is the decision that matters most.

  • A session is trivial to revoke. Delete the record on the server and the next request fails instantly. Logout, password change, and ban all work immediately.
  • A JWT is hard to revoke before it expires. Because the server keeps no state, there is nothing to delete. The token stays valid until exp, even if the user logged out or was banned a minute ago.

The common mitigations all chip away at the stateless benefit: keep access tokens short lived and refresh them, or maintain a denylist of revoked token IDs, which reintroduces exactly the server side lookup JWTs were meant to avoid.

Statefulness, scale, and storage

JWTs shine when you want to avoid a lookup on every request, for example across many independent services that would otherwise all query one session store. The token is self contained, so any service that trusts the signing key can validate it without a shared database.

Sessions shine when you control the clients and want simple, immediate control. The cost is a lookup per request, which a cache handles easily for most applications. Storage also differs: a session ID in an HttpOnly cookie is shielded from JavaScript, while JWTs are often placed in storage that scripts can read, widening the attack surface. Where to keep a token is its own topic, covered in where to store JWT tokens.

Session cookieJWT
State livesservertoken
Lookup per requestyesno
Revoke instantlyyesno
Cross serviceneeds shared storeself contained

If you want to see what a JWT actually carries, decode one in the JWT Decoder. It runs entirely in your browser, so the token never leaves your device. To understand the signing choices behind the token, see JWT HS256 vs RS256.

The practical takeaway: choose sessions when immediate revocation and simple control matter most, and choose JWTs when stateless validation across services is worth accepting delayed revocation, which you then manage with short lifetimes.