Quick Answer

A cookie is a small piece of data the browser stores and sends back automatically — it is a transport mechanism, not an authentication method. A session keeps user state on the server and gives the browser only an id, usually in a cookie. A token, typically a JWT, carries the user data itself and is verified by signature rather than looked up. So the real choice is session versus token; the cookie is how either one travels.

Why This Comparison Confuses People

The three are usually presented as a menu to choose from, which is why the topic feels slippery. They are not the same kind of thing.

  • A cookie is a storage and transport mechanism. The server sets it, the browser stores it and attaches it to every subsequent request automatically.
  • A session is a server-side state approach. The user's data lives on the server; the browser only holds an identifier.
  • A token is a self-contained credential. The user's data lives inside the token itself, signed so it can be trusted.

A cookie can carry a session id. A cookie can also carry a token. A token can instead be sent in an Authorization header. So the genuine architectural decision is session versus token — where does the state live? — and the cookie question is a separate one about how it travels.

Once that clicks, the rest of the topic becomes straightforward.

How Cookies Work

HTTP is stateless: each request arrives with no memory of the last. Cookies were invented to fix exactly that.

// Server sets it once
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax; Max-Age=3600

// Browser then sends it automatically on every request to that site
Cookie: session_id=abc123

The automatic part is both the convenience and the risk. You write no code to attach it — and neither does an attacker, which is the basis of cross-site request forgery.

The flags are the security model, and each one closes a specific hole:

  • HttpOnly — JavaScript cannot read it, so an XSS flaw cannot steal it.
  • Secure — only sent over HTTPS, so it cannot be sniffed in plaintext.
  • SameSite — controls whether it is sent on requests originating from other sites. Strict never sends cross-site; Lax allows top-level navigation, which is the sensible default.
  • Max-Age / Expires — without these it is a session cookie and disappears when the browser closes.

Cookies are also small — roughly 4 KB — and are sent on every request to the domain, including images and scripts. Storing anything large in one silently taxes the whole site.

Sessions: State on the Server

With sessions, the server keeps the real data and the browser holds only a meaningless id.

Login:
  server creates  { "abc123": { userId: 42, role: "admin" } }   (memory / Redis / DB)
  server replies  Set-Cookie: session_id=abc123; HttpOnly

Each later request:
  browser sends   Cookie: session_id=abc123
  server looks up abc123  →  userId 42, role admin

The id reveals nothing on its own. Everything of value stays server-side, which brings the main advantage: instant revocation. Deleting the session entry logs the user out immediately, everywhere. Ban an account and their next request fails. You can list active sessions and end one device's access.

The cost is that every request performs a lookup, and that store must be shared across all your servers. In-memory sessions break the moment you run two instances behind a load balancer, because a user's session lives on only one of them. The standard fix is Redis, which is fast but is another service to run.

For a normal web application — one server or a few, users logging in and out, an admin who may need to revoke access — sessions are a perfectly modern choice, despite often being described as legacy.

Tokens: State in the Credential

With tokens the server stores nothing. The user's identity travels inside the credential, signed so it cannot be forged.

Login:
  server signs { userId: 42, role: "admin", exp: ... } with a secret
  server returns the token

Each later request:
  client sends  Authorization: Bearer <token>
  server verifies the signature — no lookup at all

The advantages follow from having no shared store. Any server holding the secret can verify any token, which suits microservices and horizontally scaled APIs. Mobile clients avoid cookie handling entirely. And a token can be issued by one service and trusted by another.

The disadvantage is the mirror image: you cannot revoke it. There is nothing to delete. A stolen token works until it expires, and logging out only means the client discards its copy. The mitigation is short expiry plus a stored refresh token — which quietly reintroduces server-side state for the part that needed revoking.

Tokens are also larger than a session id and are sent on every request, and their payload is readable by anyone holding them, since signing is not encryption.

Which to Choose

Pick by what your application actually needs, not by which sounds more current.

Choose sessions when you are building a normal server-rendered or single-server web app; when you need to revoke access immediately; when an admin must be able to end a user's session; or when you simply want fewer moving parts. Sessions in an httpOnly cookie are the simplest thing that is genuinely secure.

Choose tokens when several independent services must verify identity without sharing a session store; when mobile or third-party clients are involved; or when you are issuing credentials for an API that others consume.

The common hybrid, and a sensible default for a serious application: a short-lived access token for authorising requests, plus a long-lived refresh token stored server-side in an httpOnly cookie. Requests stay stateless and fast, and revocation still works because the refresh token can be deleted.

Whatever you choose, the cookie flags matter more than the choice itself. An httpOnly, Secure, SameSite cookie protects both approaches; a readable cookie undermines both equally.

Frequently Asked Questions

Is a session stored in a cookie? No — only the session id is. The actual data lives on the server in memory, Redis or a database. The cookie carries a meaningless identifier that the server exchanges for the real state on each request.
Can I use JWTs in cookies? Yes, and it is often the better choice. Storing a JWT in an httpOnly cookie keeps it out of reach of JavaScript, which removes the XSS-theft risk that localStorage carries. Add SameSite and Secure to cover CSRF and transport.
Are sessions outdated? No. They are the simplest secure option for most web applications and they support instant revocation, which stateless tokens cannot. Tokens are better suited to distributed APIs and mobile clients, not automatically better overall.
What does httpOnly actually protect against? It stops JavaScript reading the cookie, so a cross-site scripting flaw cannot steal it. It does not protect against CSRF, because the browser still attaches the cookie automatically — that is what SameSite is for.
Why can't I just store the user id in a cookie? Because the user can edit it. A plain cookie saying userId=42 can be changed to userId=1 and you would trust it. Session ids work because they are random and meaningless, and tokens work because a signature proves the contents were not altered.