← Back to Security

Session-Based Authentication vs. JWTs

Session and JWT authentication compared by state storage and validation flow.

SecurityAuthenticationJWT

Session-based authentication and JWT-based authentication both answer the same question: how does the server recognise a user on the second request and every request after that? They differ mainly in where the session state lives and how it is validated.

In session-based authentication, the server keeps the important state. After login, the backend creates a session record, stores it in memory, a database, or a cache such as Redis, and returns a session identifier to the browser. On later requests, the browser sends that identifier back, usually in a cookie. The server looks up the session record and decides whether the user is authenticated.

This model has a few practical strengths:

  • the browser only holds an opaque identifier, not the user claims themselves
  • the server can revoke a session immediately by deleting it
  • changing permissions takes effect on the next lookup because the server remains authoritative
  • cookies stay small because the identifier is short

The main cost is operational. The session store must be available on every authenticated request. In a multi-node or multi-region system, sessions need a shared backing store or sticky routing. If the session store is slow or unavailable, login traffic and authenticated traffic suffer together.

A JWT, short for JSON Web Token, shifts more state to the client side. After login, the server issues a signed token containing claims such as user ID, issuer, audience, expiry time, and sometimes roles or scopes. On later requests, the client sends the token, and the server verifies the signature instead of looking up a central session record.

That makes JWTs attractive when many services need to validate identity without calling a central session store on every request. It is common in service-to-service systems, API ecosystems, and federated identity flows.

The tradeoff is revocation and freshness. A signed JWT remains valid until expiry unless the system adds extra machinery such as deny lists, token introspection, or short lifetimes plus refresh tokens. If a user is disabled or their role changes, old tokens may still carry stale claims until they expire. Token size is another cost. A JWT with several claims is larger than a short session ID, so it adds more bytes to every request.

A frequent misconception is that JWTs are inherently more secure because they are signed. Signing proves integrity, not secrecy. Anyone who obtains the token can usually read its claims unless the token is separately encrypted. Another misconception is that JWTs remove state entirely. Real systems often still keep state for refresh tokens, logout, rotation, or abuse detection.

For browser applications, transport choices matter as much as token format. Storing a JWT in an HttpOnly, Secure, SameSite cookie is usually safer than putting it in local storage because JavaScript cannot read an HttpOnly cookie. If a cookie is used, CSRF defences still matter. If a bearer token is stored in JavaScript-accessible storage, XSS becomes more dangerous.

The design choice is therefore not “old versus modern”. It is a tradeoff between server-side control and decentralised verification. Sessions are usually simpler when one web application controls both frontend and backend and wants immediate revocation. JWTs are useful when identity must travel across service boundaries and the system can tolerate expiry-based revocation semantics. Choose the model that matches your trust boundaries, not the one that sounds more fashionable.