What is the difference between the HTTP 'Set-Cookie' response header and the 'Cookie' request header, and what exactly does a browser send back on the next request?
answer
- Set-Cookie = response, one per cookie, with attributes
- Cookie = request, one header, name=value pairs joined by '; '
- attributes never come back — server sees names and values only
- rewrite must restate every attribute or flags are lost
- document.cookie: write one, read all, no HttpOnly
basics
~20 sSet-Cookie goes server to client, one header per cookie, carrying name=value plus attributes. Cookie goes client to server as a single header listing only the matching name=value pairs separated by '; '. Attributes such as Path, Expires and HttpOnly are never sent back.
solid answer
~40 s`Set-Cookie` is a **response** header: the server sends one per cookie, each carrying `name=value` plus attributes (`Domain`, `Path`, `Expires`/`Max-Age`, `Secure`, `HttpOnly`, `SameSite`). The attributes are storage instructions for the browser.\n\n`Cookie` is a **request** header: the browser collects every stored cookie whose scope matches the outgoing request and sends them in **one** header as `name=value` pairs joined by `\"; \"`:\n\n```\nCookie: sid=abc; theme=dark; locale=de\n```\n\nThe crucial asymmetry: **attributes are never echoed back**. The server sees names and values only. It cannot tell from the request whether a cookie was `Secure`, `HttpOnly`, host-only or domain-wide, what its path was, or when it expires — which is why two same-named cookies with different scopes are indistinguishable on the wire, and why servers must re-state all attributes every time they rewrite a cookie.
code
http · 1 lineHTTP/1.1 200 OK\nSet-Cookie: sid=abc123; Path=/; Secure; HttpOnly; SameSite=Lax\nSet-Cookie: theme=dark; Path=/; Max-Age=31536000\n\nGET /dashboard HTTP/1.1\nHost: example.com\nCookie: sid=abc123; theme=darkgo deeper
State the direction of each header, that Set-Cookie is one per cookie with attributes, and that Cookie is a single header of name=value pairs.
Emphasise that attributes never return, so rewrites must restate them, and cover the name/value grammar and encoding requirement.
Draw out the security consequence — accidental flag downgrade on rewrite, invisibility of HttpOnly at the server, cookie size as a per-request cost — and centralise cookie issuance.
Treat cookie issuance as a platform concern: one library owning names, scopes and flags, with size budgets and audit, rather than each service hand-writing headers.
## Two headers, two directions\n\nCookies are a small state-management layer bolted onto stateless HTTP, and they use two distinct headers.\n\n**`Set-Cookie` (response, server → client).** One cookie per header field. The field value is `name=value` followed by optional attributes separated by semicolons:\n\n```\nSet-Cookie: sid=abc123; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=3600\nSet-Cookie: theme=dark; Path=/; Max-Age=31536000\n```\n\nTo set three cookies, send three `Set-Cookie` header fields. You cannot pack several cookies into one.\n\n**`Cookie` (request, client → server).** The browser gathers all stored cookies whose `Domain`, `Path`, `Secure` and `SameSite` constraints match the request it is about to make, and sends them in a **single** header, pairs separated by a semicolon and a space:\n\n```\nCookie: sid=abc123; theme=dark\n```\n\n## The asymmetry that trips people up\n\nAttributes are **instructions to the browser, not data for the server**. They travel out on `Set-Cookie` and never come back. On the request the server sees only names and values.\n\nConsequences worth stating in an interview:\n\n- The server **cannot read** a cookie's expiry, path, domain or flags from an incoming request. If it needs that knowledge it must keep it server-side or encode it in the value.\n- **Every rewrite must restate every attribute.** Sending `Set-Cookie: sid=newvalue` with no attributes does not "update the value and keep the flags" — it creates a host-only, default-path cookie with no `Secure`, no `HttpOnly` and default `SameSite`, which can silently downgrade a session cookie's security. This is a genuine vulnerability pattern, not a nit.\n- `HttpOnly` is invisible from the request too: a server cannot verify that the cookie it received was the `HttpOnly` one.\n- Cookies are attached by **origin scope, not by application logic**. Every matching request carries them, including images, stylesheets, XHR and prefetches — which is why large cookies are a real bandwidth cost and why cross-site requests need `SameSite`.\n\n## Grammar\n\nThe `name=value` pair is the only mandatory part. The name is a token: no control characters, spaces, or the separators `( ) < > @ , ; : \\ \" / [ ] ? = { }`. The value may not contain control characters, whitespace, commas, semicolons or backslashes unquoted; a value may be wrapped in double quotes, and the quotes are then part of the value as far as most servers are concerned. Because the safe character set is so narrow, anything non-trivial — JSON, binary, user text — must be encoded first, conventionally with percent-encoding or base64url. Frameworks usually do this for you; hand-built headers usually get it wrong the first time a value contains a semicolon or a space.\n\nAn empty value is legal (`sid=`), which is why deletion conventionally sends an empty value with an immediate expiry.\n\n## Ordering and duplicates in the Cookie header\n\nWhen several stored cookies match, the browser serialises them with cookies having a **longer path first**, and among equal paths, earlier-created first. Servers must not depend on this ordering for correctness beyond the duplicate-name case, and generally should avoid ever having two cookies with the same name.\n\n## Reading them correctly in code\n\nOn the server, parse `Cookie` by splitting on `;`, trimming whitespace, splitting each pair on the **first** `=` only (values can legitimately contain `=`, for example base64 padding). On the client, `document.cookie` returns exactly the same flat `name=value; name=value` string — no attributes, and no `HttpOnly` cookies at all. Assigning to `document.cookie` sets **one** cookie per assignment; it is a write-one/read-all API, which surprises nearly everyone the first time.\n\n## Mental model\n\n`Set-Cookie` is a filing instruction: "store this under this name with these rules". `Cookie` is the browser handing back the contents of the matching files — labels and contents only, never the filing rules.
- Can the server tell from an incoming request whether a cookie was set with HttpOnly and Secure?No. The Cookie header carries only name=value pairs; every attribute is a storage instruction consumed by the browser and never echoed back. If the server needs that assurance it must control how the cookie is issued and, where it matters, use a name prefix whose acceptance rules the browser enforces at set time.
- What happens if you refresh a session cookie's value with 'Set-Cookie: sid=new' and no attributes?You replace the cookie with a host-only, default-path cookie that has no Secure, no HttpOnly and default SameSite — silently downgrading its protections, and possibly creating a second cookie if the original had a different Domain or Path. Every write must restate the full attribute set, which is why cookie writing belongs in one shared helper.
Set-Cookie is the note the shop staples to your package saying where to file it and when to shred it. The Cookie header is you handing the package contents back — the filing note stayed with the filing cabinet.
saying these in an interview costs you the question
- Thinking the browser sends attributes such as Path or Expires back in the Cookie header
- Trying to set several cookies with one comma-separated Set-Cookie header
- Assuming Set-Cookie with only name=value preserves the previous attributes
- Expecting document.cookie to expose HttpOnly cookies
- Splitting a cookie pair on every '=' instead of only the first