↑↓ to move ↵ to open Esc to close Browse all tools

Developer guide

JWT vs Session Cookies: Where the Login State Lives

Once a user has signed in, something has to remind the server who they are on the next request. Two designs dominate: a session cookie that carries only an ID while the server keeps the record, and a JSON Web Token that carries the record itself, signed so it cannot be altered. They solve the same problem with opposite trade-offs. Here is how each works, by the specifications, and how to choose.

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 cookieJWT
What the browser holdsAn opaque session ID, in a cookieA signed token carrying the claims themselves
Where the state livesOn the server, keyed by the IDInside the token; the server stores nothing per session
How it travelsThe browser attaches the cookie to every request for the domain, automaticallyThe client puts it in an Authorization: Bearer header, or in a cookie
Logging out or revokingDelete the server-side record; the ID is dead at onceThe token stays valid until exp unless the server keeps a block list
Reading the contentsNothing to read; the ID is randomAnyone holding the token can decode the claims; only the signature needs the key
Size per requestA few dozen bytesHundreds of bytes to a few kilobytes, on every call
Typical useWebsites with a server that renders pagesAPIs, 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