---
title: Sessions and cookies
summary: What a session cookie must carry, the flags that protect it, and the trade-off between signed cookies and server-side sessions.
topic: security
order: 3
updated: 2026-09-19
---

## What it is

HTTP forgets you between requests. A **session** is how the server remembers that this
browser already signed in. The browser holds a cookie; the server trusts whatever that cookie
proves. So whoever holds the cookie *is* the user, as far as the server can tell.

## Why it is a rule

- **Firesheep (2010).** Session cookies sent over plain HTTP were read off shared Wi-Fi and
  replayed. No password was ever needed. The fix was the `Secure` flag and HTTPS everywhere.
- **The Samy worm (2005).** A script injected into a MySpace profile ran in the browser of
  everyone who viewed it and made them add Samy as a friend and copy the script to their own
  profile. It reached over a million profiles in under a day. Any script running in your page
  can act as the user; `HttpOnly` at least stops it from carrying the cookie away to use
  later from somewhere else.

## How to do it

### The flags

```http
Set-Cookie: session=<value>; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=28800
```

| Flag | What it stops |
| --- | --- |
| `HttpOnly` | JavaScript cannot read the cookie, so an injected script cannot steal it |
| `Secure` | The cookie is never sent over plain HTTP |
| `SameSite=Lax` | Other sites cannot send it with a POST (see the CSRF lesson) |
| `Max-Age` | The session ends on its own, even if nobody logs out |

A cookie name starting with `__Host-` is refused by the browser unless it is `Secure`, has
`Path=/` and no `Domain`, so a subdomain cannot overwrite it.

### What goes inside

The value must be impossible to guess or forge. Two common shapes:

- **A random id** (128 bits or more) that points to a row on the server.
- **A signed value**: the data, an expiry, and an HMAC over both with a server secret.

```js
const sign = (body, exp) =>
  crypto.createHmac('sha256', SECRET).update(`${body}.${exp}`).digest('base64url')

const valid = (cookie) => {
  const [body, exp, sig] = cookie.split('.')
  if (!sig || Date.now() > Number(exp)) return false
  const want = Buffer.from(sign(body, exp))
  const got = Buffer.from(sig)
  return want.length === got.length && crypto.timingSafeEqual(want, got)
}
```

`timingSafeEqual` matters: a normal `===` stops at the first wrong character, and the time it
takes can leak how much of a guess was right.

### The life of a session

- Issue a **new** session at login. Reusing one from before login allows session fixation.
- Clear it at logout with the **same** flags it was set with. A mismatched `SameSite` or
  `Secure` on the delete leaves the cookie in place and logout silently does nothing.
- Re-check the user's role and access on every request. A cookie proves who, not what they
  may do today.

## How we do it here

Every session cookie is `HttpOnly`, `SameSite=Lax`, and `Secure` in production, is signed
with HMAC-SHA256 and carries its own expiry. Signatures are compared in constant time.
Access is checked on every request, not only at sign-in.

## Benefits

- Signed cookies need no session table: nothing to store, look up or clean.
- The flags cost nothing and remove whole attack classes.
- A short `Max-Age` limits the damage of a stolen cookie.

## Disadvantages

- **A signed cookie cannot be revoked early.** It stays valid until it expires, unless you add
  a server-side deny list or rotate the secret, which logs everyone out.
- **Server-side sessions need storage** and a lookup on every request, and they must survive
  restarts.
- Short sessions are safer but make people sign in more often.
- `SameSite=Strict` is safer again but drops the session when a user follows a link to you
  from an email.

## Checklist

- `HttpOnly`, `Secure`, `SameSite` on every session cookie.
- Values are random or signed, and compared in constant time.
- A new session at login; cleared with matching flags at logout.
- Permissions re-checked per request.

## Sources

- [OWASP — Session Management Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html)
- [MDN — Using HTTP cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)
- [Node.js — crypto.timingSafeEqual](https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b)
