Session cookies: the server remembers
MDN describes a cookie as a small piece of data a server sends to the browser, which stores it and sends it back with later requests to the same server, and names session management, sign-in status and the like, as its first purpose. The pattern: on login the server creates a record, stores it under a random identifier, and sets that identifier in a cookie. Every later request carries the cookie, the server looks the record up, and the user is known. The identifier means nothing on its own; all the state is on the server.
HTTP/1.1 200 OK
Set-Cookie: sid=9f3c…a1; Path=/; Secure; HttpOnly; SameSite=Lax
GET /account HTTP/1.1
Cookie: sid=9f3c…a1 The three attributes do the security work, as MDN defines them. Secure means the cookie is only sent over HTTPS. HttpOnly means JavaScript cannot read it, which blunts cross-site scripting. SameSite controls whether it is sent with cross-site requests: Strict only from the cookie's own site, Lax also when the user navigates there from elsewhere, None always; MDN notes that Lax is the default when nothing is set. A random, unguessable identifier, the kind the UUID generator produces, is the other requirement.
JWTs: the token remembers
RFC 7519 defines a JSON Web Token as a compact, URL-safe way of representing claims to be transferred between two parties. The claims are a JSON object; the token is three base64url-encoded parts separated by periods, a header naming the algorithm, the payload of claims, and a signature over the first two. The server keeps no record: on each request it checks the signature with its key and trusts the claims inside.
eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiI0MiIsImV4cCI6MTc5MjU4ODAwMH0 . 3nQ…kY
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIi… The RFC registers a small set of claim names: iss (who issued it), sub (who it is about), aud (who it is for; a recipient not named in it must reject the token), exp (after which it must not be accepted), nbf (before which it must not), iat (when it was issued) and jti (a unique ID that can prevent replay). The JWT decoder splits a token and shows the header and claims; the JWT generator builds and signs one for testing. The middle part is plain Base64, which is the first thing to understand about tokens: encoding is not encryption, as the hashing, encryption and encoding guide explains, and RFC 7519 itself warns that a JWT may contain privacy-sensitive information that must not be disclosed to unintended parties.
Side by side
| Session cookie | JWT | |
|---|---|---|
| What the browser holds | An opaque session ID, in a cookie | A signed token carrying the claims themselves |
| Where the state lives | On the server, keyed by the ID | Inside the token; the server stores nothing per session |
| How it travels | The browser attaches the cookie to every request for the domain, automatically | The client puts it in an Authorization: Bearer header, or in a cookie |
| Logging out or revoking | Delete the server-side record; the ID is dead at once | The token stays valid until exp unless the server keeps a block list |
| Reading the contents | Nothing to read; the ID is random | Anyone holding the token can decode the claims; only the signature needs the key |
| Size per request | A few dozen bytes | Hundreds of bytes to a few kilobytes, on every call |
| Typical use | Websites with a server that renders pages | APIs, mobile apps, services talking to services |
The revocation problem
This is the trade that decides most cases. A session record can be deleted, so logging out, changing a password or banning an account takes effect on the next request. A JWT is valid until its exp claim says otherwise, because the server deliberately keeps nothing to delete. The usual answers are short lifetimes, which RFC 6750 recommends for bearer tokens, one hour or less, paired with a refresh token that can be revoked, or a server-side block list of revoked token IDs, which quietly reintroduces the state the design set out to avoid.
Why a token must be guarded
RFC 6750 states the property plainly: any party in possession of a bearer token can use it, without proving anything else. Hence its rules: clients must always use TLS when sending bearer tokens, and servers should issue short-lived ones. Where the client stores the token matters just as much. In a browser, a token readable by JavaScript is a token readable by any injected script; an HttpOnly cookie is out of a script's reach but is sent automatically, which brings back the cross-site concerns SameSite exists for. There is no storage that is immune to both; there is only choosing which risk the rest of the design handles.
Choosing
- A website with a server and a database: session cookies. They are simpler, revocable, small, and the browser does the transport.
- An API used by mobile apps or other services, or several services that must trust a login without sharing a session store: JWTs, short-lived, over TLS, with a refresh flow.
- A single-page app talking to its own backend: either; a session cookie with SameSite is usually the less error-prone choice.
- In every case: nothing secret in the payload, HTTPS everywhere, and an expiry that is as short as the use allows. The hash generator is a quick way to see what a signature algorithm such as HMAC-SHA256 produces, though verifying a real token needs the key.
Frequently asked questions
What is a JWT?
RFC 7519 defines a JSON Web Token as a compact, URL-safe means of representing claims to be transferred between two parties. The claims are a JSON object, carried as the payload of a signed structure, and the token is written as base64url-encoded parts separated by periods: header, payload, signature. The signature lets the receiver verify who issued it and that nothing was changed.
What is a session cookie?
A cookie that holds a session identifier. MDN lists session management as the first use of cookies: sign-in status, cart contents and other details the server needs to remember. The browser stores the cookie and sends it back with later requests to the same server; the server looks up the session by its ID. A session cookie in the strict sense is one with no expiry, deleted when the browser session ends.
Is a JWT encrypted?
Not by default. A signed JWT is readable by anyone who has it; the payload is base64url, not ciphertext. RFC 7519 warns that a JWT may contain privacy-sensitive information and that disclosure must be prevented, by encrypting the token or by leaving the sensitive data out. The JWT decoder shows how much a token reveals.
Which is more secure, JWT or session cookies?
Neither by nature; each has a characteristic weakness. A cookie travels automatically, so it needs the HttpOnly, Secure and SameSite attributes MDN describes to resist cross-site scripting and request forgery. A bearer token, in the words of RFC 6750, can be used by any party in possession of it, so it must travel over TLS, live a short time and not be stored where scripts can read it.
Can I store a JWT in a cookie?
Yes, and it combines the strengths: the token is self-contained and the cookie is HttpOnly, so scripts cannot read it. The cost is that the cookie is sent automatically again, so SameSite and CSRF protection come back into play, and the size of the token is added to every request.
Sources
- RFC 7519: JSON Web Token (definition; the base64url parts separated by periods; the registered claims iss, sub, aud, exp, nbf, iat and jti; unsecured JWTs with alg none; the privacy consideration that a JWT may contain sensitive information)
- RFC 6750: The OAuth 2.0 Authorization Framework, Bearer Token Usage (any party in possession of a bearer token can use it; the Authorization: Bearer header; clients must use TLS; token servers should issue short-lived tokens of one hour or less)
- MDN Web Docs: Using HTTP cookies (Set-Cookie and Cookie headers; session management as the first use; session versus permanent cookies; Secure, HttpOnly and SameSite with Lax as the default; cookies sent with every request; the size limit of about 4 KB)