A cookie is a string the browser sends back
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.
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
| Attribute | Decides |
|---|---|
Domain | which 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 |
Secure | https only. Non-negotiable for anything that identifies a user |
HttpOnly | JavaScript cannot read it — the single best defence against a stolen session |
SameSite | whether other sites’ requests carry it. The CSRF control |
Max-Age / Expires | how long it lives. Neither = until the browser closes |
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.
| Value | A request from another site carries it… |
|---|---|
Strict | never — 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 |
None | always — and the browser requires Secure with it |
Sessions, and what belongs in a cookie
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.
# 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
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";
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.