Cookies

Set a cookie that goes exactly where you meant it to, and nowhere else.

08of 18
≈ 13Min read
5Parts

That is the whole mechanism: a name, a value, and a set of rules about when to send it back. Everything difficult about cookies is those rules — and they are enforced by the browser, not by your server.

set once, returned on every matching request
HTTP/1.1 200 OK
Set-Cookie: sid=8f3d21; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=1209600

GET /orders HTTP/1.1
Cookie: sid=8f3d21

The attributes, and what each one decides

AttributeDecides
Domainwhich hosts get it. Omitted = this host only. Domain=doors.test = every subdomain too
Path=/which paths get it — a prefix match, and / is almost always right
Securehttps only. Non-negotiable for anything that identifies a user
HttpOnlyJavaScript cannot read it — the single best defence against a stolen session
SameSitewhether other sites’ requests carry it. The CSRF control
Max-Age / Expireshow long it lives. Neither = until the browser closes
Omitting Domain is narrower, not wider

People read Domain=doors.test as "restrict it to this domain". It is the opposite: it widens the cookie to every subdomain, including the marketing site somebody else runs. Leave it out unless you need sharing.

SameSite, in the only three cases that exist

Lax is the default in current browsers and is the right answer for a session cookie: a link from an email still works, and a cross-site form post — the shape of a CSRF attack — does not carry it.

None is for a cookie that genuinely has to work inside somebody else's page: an embedded widget, an SSO iframe, an API on another domain. It is also the one third-party tracking used, which is why it is being restricted further.

ValueA request from another site carries it…
Strictnever — not even when a link is clicked. A logged-in user arriving from Google looks logged out
Lax (the default)only on a top-level navigation with a safe method: a clicked link, not a form POST, not an image or a fetch
Nonealways — and the browser requires Secure with it

A session id is opaque and revocable: delete the row and the session is gone. A signed token needs no lookup but cannot be revoked before it expires. Both belong in an HttpOnly cookie; neither belongs in localStorage, where any injected script can read it.

Never put anything in a cookie you would mind a user editing, unless it is signed. The browser will send back whatever it is given.

two designs
# a session id — the server keeps the state
Set-Cookie: sid=8f3d21; HttpOnly; Secure; SameSite=Lax; Path=/

# a signed token — the state is in the cookie
Set-Cookie: token=eyJ…; HttpOnly; Secure; SameSite=Lax; Path=/

Reading and deleting

and why the good ones are invisible here
document.cookie                 // only the cookies WITHOUT HttpOnly
document.cookie = "theme=dark; Path=/; Max-Age=31536000";

// deleting is setting it again, expired
document.cookie = "theme=; Path=/; Max-Age=0";
Delete with the same attributes

A cookie is identified by name + domain + path. Deleting sid at Path=/ does nothing to the sid set at Path=/admin, and the two of them will both be sent, in an order nobody has specified.

Practice