# Sessions vs tokens

> A random ID the server looks up, or a signed token the server verifies — and what each costs at logout.

Concept · Web services · https://levelcode.org/concepts/sessions-vs-tokens

After a user logs in, every later request has to prove who they are. There are two common designs.

## Server-side sessions

The server creates a random, unguessable session ID, stores `session_id → user_id, expiry` in a database or Redis, and sends the ID in a cookie:

```http
Set-Cookie: sid=Jq8x…; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=1209600
```

Each request looks the ID up.

- **Revocable instantly:** delete the row and the user is logged out everywhere.
- **Small cookie** that holds no data.
- **Costs a lookup per request.** That's cheap in Redis, and the lookup is usually not your bottleneck.

## Signed tokens (JWTs)

The server signs a token containing the claims (`sub`, `exp`, roles) and the client sends it on each request, usually as `Authorization: Bearer …`. Any server holding the verification key can check it **without a lookup**.

- **Good across services and organisations:** an API gateway or a third party can verify it with a public key.
- **Hard to revoke.** A stolen token stays valid until `exp`. Teams therefore keep access tokens short-lived (5–15 minutes) and pair them with a long-lived **refresh token** that the server *does* store and can revoke. At that point you're keeping server-side state again.
- **Everyone can read it.** A JWT is signed, not encrypted, so never put secrets in it.

## Choosing

| Situation | Choose |
|---|---|
| A web app talking to its own backend | Sessions in an `HttpOnly` cookie |
| Service-to-service calls, or many APIs behind a gateway | Short-lived signed tokens |
| Third parties calling your API | OAuth access tokens (often JWTs) |

"JWTs make the server stateless" is mostly a myth for user-facing apps. You'll want logout, "sign out other devices" and banning, and all of them need state.

## Storage in the browser

- **`HttpOnly` cookies** can't be read by JavaScript, so an [XSS](https://levelcode.org/concepts/xss-and-csp) bug can't steal them. Set `SameSite=Lax` or `Strict` to block most CSRF, and add a CSRF token for state-changing requests if you need cross-site forms.
- **Avoid `localStorage` for tokens.** Any injected script can read it.

Whatever you choose, verify the [password](https://levelcode.org/concepts/password-hashing) properly first, and rotate the session ID at login to prevent session fixation.
