JWT vs Sessions: Making the Right Choice
November 4, 2025 · 5 min read
Every time authentication comes up in a tech discussion, someone says "just use JWTs." Someone else says "sessions are more secure." Both camps have real points, and choosing the wrong one for your use case will create problems that are annoying to fix later.
Here's how I think about it—informed by building AegisAuth, a modular authentication platform designed to support both.
The Core Tradeoff
Session-based auth stores state on the server. When a user logs in, you create a session record in your database (or Redis), generate a random session ID, and send that ID to the client as a cookie. Every request carries the cookie. Your server looks up the session, verifies it, and grants or denies access. The server is the source of truth.
JWT-based auth stores state in the token. When a user logs in, you generate a signed token containing claims (user ID, roles, expiry). The server doesn't store anything. Every request carries the token. Your server verifies the signature and trusts the claims. The token is the source of truth.
When Sessions Win
Sessions are the right default for traditional web apps — anything with server-rendered pages, a single persistent backend, and users who log in expecting sessions to end when they log out.
The killer feature of sessions is instant revocation. Ban a user? Delete the session row. Token compromised? Invalidate immediately. With JWTs, you can't un-sign a token that's already been issued. If a JWT with a 24-hour expiry gets stolen, that token is valid for 24 hours unless you implement a blocklist — which is stateful, which defeats part of the point.
When JWTs Win
JWTs shine in stateless, distributed systems. If your API is consumed by a mobile app, a third-party client, or multiple backend services that don't share a session store, passing a self-contained signed token is much simpler than requiring every service to hit a central session database.
Microservices are the other strong case. Service A can verify a JWT from Service B without making a network call — it just checks the signature. This reduces latency and removes the session store as a single point of failure.
The Tradeoffs Nobody Mentions
Token size. A session ID is typically 32 bytes. A JWT with a few claims is typically 300–500 bytes. At scale, this adds up in headers on every request.
Clock skew. JWT expiry is time-based. If your server's clock is even slightly wrong, tokens expire at the wrong time. This is rare but has caused real production incidents.
Logout UX with JWTs is harder than it looks. You either implement a blocklist (stateful again), use short expiry with refresh tokens, or accept that "logout" just clears the client-side token — with the server still accepting it until it expires. Most apps do option 3 and quietly hope nobody notices.
How AegisAuth Handles Both
AegisAuth is designed as a drop-in auth service that supports both strategies behind a unified interface. The consuming application doesn't need to handle session storage or token signing — it sends a credential, receives an opaque session handle or a JWT, and uses whichever the strategy is configured for.
The design principle: the auth strategy should be swappable without the application layer knowing. You start with sessions, switch to JWT when you go multi-service, and the integration code doesn't change.
The Practical Answer
Use sessions by default for web apps. Add JWTs when you have a genuine distributed systems requirement — not because they sound more modern. And whatever you do, don't store sensitive data in the JWT payload. It's signed, not encrypted.
AegisAuth is currently in development. View on GitHub →