Guide
Sessions vs Tokens: Where Cookies Fit
Compare server-side sessions and signed tokens such as JWT, when to keep them in cookies, why storage choice matters, and how to log out safely.
Key takeaways
- A cookie is a browser mechanism, a session is a server-side record and a token is a signed piece of data; a cookie can carry either.
- Server-side sessions are easy to revoke; signed tokens need extra machinery such as short lifetimes or a deny list.
- A signature proves integrity, not secrecy: do not put sensitive data in the claims of a merely signed token.
- Any script on a page can read localStorage, while an HttpOnly cookie hides the value but brings CSRF into scope.
- Regenerate the session identifier at login, and enforce idle and absolute timeouts on the server.
Three ideas that get mixed up
Developers often say cookies versus tokens, but the comparison is slightly off. A cookie is a transport and storage mechanism in the browser. A session is a server-side record of a logged-in user. A token is a piece of data that proves something about the user. A cookie can carry a session identifier, and it can also carry a token. So the real design choice is between server-side state and self-contained credentials, and then a second decision about where the browser keeps them.
How do server-side sessions work?
In the classic model, the server creates a random, unguessable identifier when a user signs in, stores a record somewhere under that identifier, and sends the identifier to the browser in a cookie. On every request, the browser returns the cookie, and the server looks up the record. The cookie contains no user data at all, only a lookup key. If the server wants to end the session, it deletes the record, and the identifier becomes useless immediately.
- Revocation is simple: delete the record.
- The cookie stays small and reveals nothing if someone inspects it.
- The server needs a shared store such as a database or an in-memory cache when it runs on several machines.
- Every request incurs a lookup, which is usually fast but is still a dependency.
Session identifiers must come from a cryptographically secure random generator and be long enough that guessing is impractical. Do not derive them from a username, time or counter. Regenerate the identifier after login and after privilege changes (MDN advises regenerating session cookies whenever a user authenticates), to avoid session fixation, where an attacker plants a known identifier before the victim signs in.
How do signed tokens work?
A self-contained token, of which JSON Web Token is the best-known format, packs claims such as a user identifier and an expiry into a signed string. A server holding the signing key can verify the token without any lookup. This is attractive for services that must scale across many machines or that authorise calls between different services.
The trade-offs are real. A signed token is valid until it expires unless you add extra machinery. Revocation then needs a deny list, short lifetimes combined with refresh tokens, or a version number checked against a store, and at that point you have reintroduced server-side state. Tokens are also easy to make too large, and putting sensitive data in a token that is merely signed, not encrypted, exposes it to anyone who can read it. A signature proves integrity, not secrecy.
Where should the browser keep a token?
For browser applications, the storage choice matters as much as the format. The two common options each have a weakness.
- Web storage such as localStorage is readable by any script running on the page. A single cross-site scripting flaw can expose every token stored there.
- A cookie marked HttpOnly cannot be read by scripts, but it is attached automatically to requests, which brings cross-site request forgery into scope and means you need SameSite settings and anti-forgery measures.
Many security guidelines favour an HttpOnly, Secure cookie with an appropriate SameSite value for browser sessions, because it removes the easiest theft route. Native mobile applications and server-to-server calls have different constraints and often send tokens in the Authorization header instead. Choose according to the client you actually have rather than copying one pattern everywhere.
Combining the two: cookie holds the session or token
It is entirely normal to put a signed token inside a cookie, or to put a random session identifier inside one. In both cases the cookie attributes discussed elsewhere apply. For a browser login, a solid baseline is a cookie with HttpOnly, Secure, a SameSite value of Lax or Strict, a narrow scope and, where possible, the __Host- prefix. Then decide what the value is. A random identifier is simplest. A signed token is reasonable if you accept its revocation model.
How should lifetimes, renewal and logout work?
Good session design uses two clocks: an idle timeout, which ends the session after a period of inactivity, and an absolute timeout, which ends it after a fixed maximum regardless of activity. The server enforces both, whatever the cookie lifetime says. Rotating the identifier periodically, and on sensitive actions, limits the value of a stolen copy.
Logout needs both sides. The server should invalidate the session or token record where it can, and send a Set-Cookie that clears the cookie with matching name, domain and path. A logout that only deletes the cookie in the browser leaves a copied value valid on the server.
Server-side session or signed token
| Aspect | Server-side session | Signed token |
|---|---|---|
| What the cookie holds | A random lookup identifier | Signed claims such as user id and expiry |
| Revocation | Delete the record | Needs short lifetimes, a deny list or a version check |
| Server work per request | A store lookup | Signature check, no lookup |
| If the value is read | Reveals nothing | Claims are readable unless encrypted |
How do I choose between them?
- A single web application with one backend usually does well with server-side sessions in an HttpOnly cookie.
- A system of many services, or an API consumed by non-browser clients, may suit signed tokens, with short lifetimes and a plan for revocation.
- If you adopt tokens, still keep the browser delivery safe, and avoid exposing them to page scripts.
Common mistakes
Teams sometimes store long-lived tokens in localStorage for convenience, place personal data in unencrypted token claims, skip expiry checks, or forget to rotate secrets. Others treat the signature as proof the user is still allowed to act, when the account may have been disabled since the token was issued. Review these points with your security team, and rely on well-maintained framework libraries rather than writing session handling from scratch.
Sources and further reading
- MDN: Using HTTP cookies: session and permanent cookies, deleting a cookie, regenerating session cookies at login, and why cookies suit storage poorly compared with Web Storage.
- MDN: Set-Cookie header: each attribute (Domain, Path, Expires, Max-Age, Secure, HttpOnly, SameSite, Partitioned) and the cookie prefix rules.
- MDN: Cross-site request forgery (CSRF): CSRF tokens, Fetch metadata headers such as Sec-Fetch-Site and SameSite as defence in depth.
Frequently asked questions
Are cookies and sessions the same thing?
No. A cookie is a browser storage and transport mechanism. A session is a server-side record. The cookie usually carries only an identifier that points to that record.
Is a JWT more secure than a session cookie?
Not by itself. A JWT is signed, which protects integrity, but it is not hidden unless encrypted, and it is harder to revoke. Security depends on how you store, transmit and expire it.
Should I keep tokens in localStorage?
Be cautious. Any script on the page can read localStorage, so a cross-site scripting flaw can expose the token. An HttpOnly cookie avoids that, at the cost of needing CSRF protections.
What does session fixation mean?
It is an attack in which the attacker sets a known session identifier before the victim logs in. Issuing a fresh identifier after login prevents it.