The short answer
Quick answer: After you log in, the server needs a way to recognise your later requests. With sessions, the server stores your login state and gives the browser a random session ID in a cookie; every request sends the ID, and the server looks it up. With JWTs (JSON Web Tokens), the server gives the client a signed token that contains your identity; each request sends the token, and the server only verifies the signature, with no lookup. Sessions are simple, easy to revoke and the sensible default for a web application. JWTs avoid a shared session store, which helps when many services or outside parties need to verify identity, but they are hard to revoke before they expire.
The problem: HTTP forgets
HTTP is stateless. Each request stands alone, and the server has no built-in memory that the person requesting /account is the one who logged in a moment ago. Something must travel with every request to prove it.
Two terms to keep apart:
- Authentication: proving who you are (logging in).
- Authorisation: deciding what you are allowed to do.
Both sessions and JWTs are ways of carrying the result of authentication from one request to the next.
How sessions work
- You submit your username and password.
- The server verifies them. See how passwords should be stored.
- The server creates a session record, such as user ID, creation time and expiry, in a store: a database or an in-memory store such as Redis.
- It sends the browser a cookie containing a long, random session ID.
- The browser returns the cookie automatically on every request to that site.
- The server looks up the ID, finds the session and knows who you are.
- Logging out deletes the record. The ID becomes meaningless.
Set-Cookie: sid=8f3a9c...; HttpOnly; Secure; SameSite=Lax; Path=/
The session ID is an opaque reference. It carries no information. All the state lives on the server, which is why this is called stateful authentication.
Strengths
- Instant revocation. Delete the session and access ends immediately. "Log out everywhere" is trivial.
- Small cookie. Nothing sensitive is held by the client.
- Simple and mature. Every web framework has it built in.
Costs
- A lookup on every request. In practice this is a sub-millisecond read from a fast store.
- Shared state. With several servers, they must all reach the same session store.
- Cookies are tied to a domain, which is awkward across unrelated domains and for some non-browser clients.
The OWASP Session Management Cheat Sheet describes how to do this well.
How JWTs work
A JWT, defined in RFC 7519, is a string in three parts separated by dots:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiIsImV4cCI6MTc2MDAwMDAwMH0.Xk3v...
header payload signature
- Header: the token type and signing algorithm.
- Payload: claims about the user and the token.
- Signature: computed over the header and payload with a secret or private key.
A decoded payload:
{
"sub": "42",
"role": "editor",
"iat": 1759996400,
"exp": 1760000000
}
The flow:
- You log in.
- The server builds the payload and signs it.
- The client stores the token and sends it with each request, usually in an
Authorization: Bearer <token>header. - The server verifies the signature and checks the expiry. If both pass, it trusts the claims. No database lookup is needed.
Two points are frequently misunderstood:
- A JWT is signed, not encrypted. The payload is only Base64-encoded. Anyone holding the token can read it. Never put secrets in one.
- The signature prevents tampering. If someone changes
"role": "editor"to"role": "admin", the signature no longer matches and the token is rejected.
Signing can be symmetric (one shared secret, HS256) or asymmetric (sign with a private key, verify with a public key, such as RS256 or ES256). With asymmetric signing, other services can verify tokens without being able to create them. See how public key cryptography works.
The revocation problem
Because the server keeps no record, a valid JWT stays valid until it expires. If a user logs out, changes their password, is banned, or has a token stolen, that token still works.
The usual answers:
| Approach | How it works | Trade-off |
|---|---|---|
| Short expiry | Access tokens live for minutes | Limits the window; users need a way to get new tokens |
| Refresh tokens | A long-lived token, stored server-side and revocable, is exchanged for new access tokens | Reintroduces server state |
| Deny list | The server keeps a list of revoked token IDs and checks it on each request | A lookup on every request, which is what JWTs were meant to avoid |
| Token version | Store a version number per user; reject tokens with an older one | Also a lookup |
In other words, as soon as you need reliable revocation, you are keeping state again. The common production pattern is a short-lived access token plus a refresh token that is stored, rotated on each use, and can be revoked.
Side by side
| Sessions | JWTs | |
|---|---|---|
| Where state lives | On the server | In the token |
| Per-request cost | A store lookup | A signature check |
| Revocation | Immediate | Difficult without extra state |
| Size | A short ID | Hundreds of bytes or more, on every request |
| Scaling | Needs a shared store | Any server with the key can verify |
| Changing a user's permissions | Takes effect at once | Takes effect when the token is reissued |
| Across services and domains | Awkward | Straightforward |
| Typical use | Web applications | APIs, service-to-service calls, federated login |
Where the token is kept
This question is separate from the choice of sessions or JWTs, and it matters more for security.
| Storage | Readable by page scripts (XSS risk) | Sent automatically (CSRF risk) |
|---|---|---|
HttpOnly cookie | No | Yes; mitigate with SameSite and CSRF tokens |
localStorage / sessionStorage | Yes | No |
| In memory only | Only while the page is open | No |
A token in localStorage can be read by any script running on the page, including one injected through cross-site scripting. An HttpOnly cookie cannot be read by scripts at all. For browser applications, an HttpOnly, Secure, SameSite cookie is generally the safer place, and a JWT can be stored in one. See cookies vs localStorage vs sessions.
Common JWT mistakes
- Not verifying the signature, or only decoding the token.
- Accepting whatever algorithm the token names, including
none. Always fix the expected algorithm on the server. - Weak signing secrets that can be guessed offline.
- No expiry, or very long-lived tokens.
- Sensitive data in the payload.
- Not checking the audience and issuer, so a token minted for one service is accepted by another.
- Using JWTs as sessions with no way to revoke them.
Use a maintained library and its verification function. Do not write token parsing yourself; see never roll your own crypto.
Which should you use?
Choose sessions when:
- You are building a conventional web application with your own back end.
- You need to log users out or cut off access immediately.
- You want the simplest thing that is secure by default.
Choose JWTs when:
- Several independent services must verify identity without calling a central store.
- You are issuing tokens to third parties or using a single sign-on provider. OAuth and OpenID Connect commonly use JWTs; see how OAuth works.
- Calls are between services, with short-lived tokens.
A common hybrid: the browser holds an ordinary session cookie with a gateway or back end, and that back end uses short-lived JWTs when calling internal services.
"JWTs scale better" is true in principle and rarely decisive. A session lookup in a fast store is cheap, and most applications that adopt JWTs to avoid server state end up adding it back for revocation.
Frequently asked questions
Are JWTs more secure than sessions?
No. They are a different trade-off. Sessions are easier to secure and revoke; JWTs are easier to verify across systems.
Can a JWT be revoked?
Not by itself. You need short lifetimes, refresh tokens, or a server-side deny list.
Is the data in a JWT encrypted?
Not in a standard signed JWT. The payload is readable by anyone who has the token. The signature only prevents modification.
Where should I store a JWT in the browser?
Preferably in an HttpOnly, Secure, SameSite cookie, so scripts cannot read it.
Conclusion
Sessions keep state on the server and hand the client a meaningless ID. JWTs hand the client a signed statement and keep nothing. The first gives you control; the second gives you independence between services. For most web applications, sessions are the simpler and safer choice, and JWTs earn their place when identity has to cross system boundaries.
Related articles
- How Cookies, LocalStorage, and Sessions Differ
- How OAuth Lets You "Sign in With Google"
- How XSS Attacks Work
- How Public Key Cryptography Works
