In the JWT vs session debate, the short answer is: use server-side sessions for most traditional web apps, and use JWTs when you need stateless tokens that other services or third parties can verify without calling your auth server. Sessions make logout and revocation easy. JWTs make distributed verification easy. Most real problems come from picking one for the wrong reason.
This guide walks through how each approach works, where each fails, and how to decide.
How does session authentication work?
With session authentication, the server remembers who you are.
- The user logs in with a username and password.
- The server creates a session record (user ID, expiry, maybe roles) in a store such as Redis or a database.
- The server sends back a random, unguessable session ID in a cookie.
- On every request, the browser sends the cookie, and the server looks up the session.
The session ID itself carries no meaning. It is just a key. All the truth lives on the server, which means you can end a session instantly by deleting the record. The sessions guide covers the mechanics in more depth.
How does JWT authentication work?
A JSON Web Token is a signed, self-contained credential. It has three Base64URL-encoded parts: a header, a payload of claims, and a signature.
import jwt from "jsonwebtoken";
const secret = process.env.JWT_SECRET!;
// Issue a short-lived access token after login
export function issueAccessToken(userId: string, roles: string[]) {
return jwt.sign({ sub: userId, roles }, secret, {
algorithm: "HS256",
expiresIn: "15m",
issuer: "auth.example.com",
audience: "api.example.com",
});
}
// Verify on every request, pinning the expected algorithm
export function verifyAccessToken(token: string) {
return jwt.verify(token, secret, {
algorithms: ["HS256"],
issuer: "auth.example.com",
audience: "api.example.com",
});
}
The server that verifies the token only needs the key. It does not need to look anything up. That is the whole point: any service holding the verification key (or the public key, with asymmetric algorithms like RS256 or ES256) can trust the claims inside. Read the JWT guide for the token structure in detail.
Note that a standard JWT is signed, not encrypted. Anyone who holds it can decode and read the payload, so never put secrets or sensitive personal data in it.
JWT vs session: side-by-side comparison
| Concern | Server-side sessions | JWT |
|---|---|---|
| Where state lives | Server session store | Inside the token |
| Verification cost | Store lookup per request | Signature check, no lookup |
| Logout and revocation | Instant, delete the record | Hard until expiry without a denylist |
| Token size | Small random ID | Larger, grows with claims |
| Cross-service use | Services need access to the store | Any service with the key can verify |
| Typical transport | HttpOnly cookie | Authorization header or cookie |
| Best fit | Server-rendered and first-party web apps | APIs, mobile, service-to-service, federated identity |
Security trade-offs
Neither approach is secure or insecure by default. The details decide.
Revocation is the big JWT problem
Once a JWT is issued, it is valid until it expires. If a user logs out, changes their password, or gets their account compromised, a stolen token keeps working. Common mitigations:
- Keep access tokens short-lived (minutes, not days).
- Pair them with a longer-lived refresh token stored server-side, which you can revoke.
- Maintain a small denylist of revoked token IDs (
jti) for high-risk events.
Notice that the last two reintroduce server state. That is fine, but it means "stateless" is rarely fully true in production.
Where you store the token matters
Storing JWTs in localStorage exposes them to any script running on the page, so a single cross-site scripting bug can leak them. An HttpOnly, Secure, SameSite cookie keeps the token out of JavaScript's reach. This applies equally to session IDs and JWTs, and it is one reason sessions in cookies are a solid default for browsers.
Cookies bring CSRF risk, which SameSite=Lax or Strict plus CSRF tokens for state-changing requests handle well.
Validate JWTs strictly
Always pin the allowed algorithm, verify exp, iss, and aud, and reject unsigned tokens. Many historical JWT vulnerabilities came from libraries that trusted the algorithm named in the token header.
Scaling: are sessions really a bottleneck?
A common argument for JWTs is that sessions do not scale. In practice, a session lookup in an in-memory store like Redis is a fast key read, and a replicated Redis cluster handles very large session volumes. The scaling argument matters more when many independent services, possibly in different organizations or regions, all need to authenticate requests without sharing a store.
So the real question is not "how many users do I have?" but "how many independent parties need to verify identity?"
When to use sessions vs JWT
Choose sessions when
- You have a single web app or a small set of services behind one backend.
- You need instant logout, forced sign-out, or account lockout.
- Your clients are mainly browsers.
Choose JWT when
- Multiple services or third parties must verify identity independently.
- You are implementing OAuth 2.0 or OpenID Connect, where access and ID tokens are commonly JWTs. See the OAuth 2.0 guide.
- You serve mobile or CLI clients that call APIs directly.
The hybrid many teams use
A browser app talks to a backend-for-frontend using a session cookie. The backend-for-frontend exchanges that session for short-lived JWTs when it calls internal APIs. Users get easy revocation; services get stateless verification.
Key takeaways
- Sessions store state on the server and make revocation trivial.
- JWTs carry signed claims and let any holder of the key verify them without a lookup.
- JWT payloads are readable by anyone, so keep them free of secrets.
- Store browser credentials in HttpOnly, Secure, SameSite cookies regardless of approach.
- Short-lived access tokens plus revocable refresh tokens are the standard JWT pattern.
- Pick based on how many independent verifiers you have, not on raw user count.
Frequently asked questions
Is JWT more secure than session authentication?
No. Both can be secure when implemented correctly. Sessions are easier to revoke, while JWTs are easier to verify across services. The storage location, expiry, and validation rules matter more than the format.
Can I log out a user with JWT?
You can delete the token on the client, but a copy of it stays valid until it expires. To truly revoke access, use short expiry times, revocable refresh tokens, or a denylist of token IDs checked on each request.
Should I store JWT in localStorage or a cookie?
For browser apps, prefer an HttpOnly, Secure cookie with an appropriate SameSite setting. localStorage is readable by any script on the page, which makes token theft through XSS much easier.
Are sessions stateless?
No. Sessions are stateful by design because the server keeps a record for every active login. That state is what makes instant revocation possible.