Parse Cookie and Set-Cookie
Why it matters Cookies are the tracking ID you can actually see. Reading a Set-Cookie line properly tells you how long a site wants to remember you, whether scripts can read the cookie, and whether it may follow you onto other sites. Getting the small rules right is also what keeps logins safe.
Back to the cloakroom. Set-Cookie is the attendant handing you a ticket, with small print on the back: "valid for one year", "only show this at the main desk", "do not show it to other venues". Cookie is you handing back all your tickets for that desk at once, small print left off.
Set-Cookie: a response header asking the browser to store one cookie. One header per cookie: Set-Cookie: sid=7f3a; Path=/; Secure; HttpOnly; SameSite=Lax.
Cookie header: a request header carrying every cookie the browser chose to send, as name=value pairs joined by ; , for example Cookie: sid=7f3a; theme=dark. Only names and values travel, never the attributes.
Attribute: a piece of small print after the first ; in Set-Cookie. Some have a value (Max-Age=3600), some are flags that are simply present (Secure).
The attributes you will parse
Expires=Thu, 01 Oct 2026 00:00:00 GMT: forget the cookie at this date. Keep the text for now; the cookie jar will turn it into a time.Max-Age=3600: forget it after this many seconds. Zero or negative means delete now. If both are present, Max-Age wins.Domain=shopa.example: also send to subdomains. A leading dot (.shopa.example) is ignored, and names are compared in lower case.Path=/account: only send for addresses under this path. A value not starting with/is ignored.Secure: only over HTTPS.HttpOnly: page scripts cannot read it.SameSite=Strict,LaxorNone: whether it goes along on requests from other sites. Any other value counts as not given.Partitioned: keep a separate copy for each top-level site (CHIPS, from Leg 1).
The careful parts
Browsers are forgiving on purpose, and your parser should be too. Attribute names ignore case, so secure and SECURE both count. Spaces around = and ; are allowed. An attribute the browser does not know (Priority=High, or a typo) is skipped, not an error. Values can contain =, as in token=a=b, so split at the first = only.
Some lines are ignored completely: a first part with no = at all, or with an empty name. The current cookie rules, draft-ietf-httpbis-rfc6265bis (approved as a standard and awaiting its RFC number, checked October 2026), allow a few unusual nameless cookies; our practice browser keeps it simple and ignores them, and so will you.
parse_set_cookie("id=7f3a; Max-Age=600; domain=.ShopA.example; Path=/; secure; SameSite=lax")
# {"name": "id", "value": "7f3a", "expires": None, "max_age": 600,
# "domain": "shopa.example", "path": "/", "secure": True, "httponly": False,
# "samesite": "Lax", "partitioned": False}A common misconception
"The Cookie header tells the server when my cookie expires." It does not. The browser keeps all the small print to itself and sends only name=value. A server that wants to know how long a cookie lasts has to remember what it set.
Try it In DevTools open Application (Chrome, Edge) or Storage (Firefox), then Cookies, and pick a site. Each row is one parsed Set-Cookie: compare the Expires, Domain, Path, Secure, HttpOnly, SameSite and Partitioned columns with the fields your parser returns.
In other languages
JavaScript:
function parseSetCookie(line) {
const [first, ...attrs] = line.split(";");
const i = first.indexOf("=");
if (i < 0 || !first.slice(0, i).trim()) return null;
const c = { name: first.slice(0, i).trim(), value: first.slice(i + 1).trim() };
for (const a of attrs) {
const j = a.indexOf("=");
const key = (j < 0 ? a : a.slice(0, j)).trim().toLowerCase();
const val = j < 0 ? "" : a.slice(j + 1).trim();
if (key === "secure") c.secure = true;
else if (key === "max-age" && /^-?\d+$/.test(val)) c.maxAge = Number(val);
// ... and the rest, as in Python
}
return c;
}Go:
// Go parses both for you: http.ParseSetCookie (Go 1.23+) and http.ParseCookie
c, err := http.ParseSetCookie("id=7f3a; Max-Age=600; Secure; SameSite=Lax")
if err == nil {
fmt.Println(c.Name, c.Value, c.MaxAge, c.Secure, c.SameSite)
}PHP:
// a server sets a cookie with every attribute named:
setcookie('sid', $id, ['expires' => time() + 3600, 'path' => '/',
'secure' => true, 'httponly' => true, 'samesite' => 'Lax']);Recap
- Set-Cookie carries one cookie plus its attributes; the Cookie header carries only name=value pairs.
- Split at the first
=, compare attribute names in lower case, and skip what you do not recognise. - Max-Age must be a whole number, Path must start with
/, and a leading dot on Domain is ignored. - A line with no
=or no name is ignored entirely.
Write parse_cookie_header(text): the request's Cookie value as a dict (split on ;, then at the first =, strip both, skip pieces with no = or no name, first value of a name wins). Write parse_set_cookie(line): a dict with keys name, value, expires (raw text or None), max_age (int or None), domain (lower case, leading dot removed, or None), path (only if it starts with /), secure, httponly, partitioned (booleans) and samesite ("Strict", "Lax", "None" or None), or None for a line with no = or an empty name.
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.