Set-Cookie auth response
Creates or updates a cookie in the browser — the server's mechanism for persisting state across HTTP requests.
What it does
Set-Cookie instructs the browser to store a cookie and send it back on future matching requests via the Cookie header. It's how servers create sessions, remember authentication state, store preferences, and maintain any persistent state across stateless HTTP requests.
Unlike most HTTP headers, Set-Cookie can appear multiple times in the same response — each instance sets one cookie.
Syntax
Set-Cookie: <name>=<value>[; <attribute>]*
Examples:
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax; Path=/
Set-Cookie: theme=dark; Max-Age=31536000; Path=/; SameSite=Lax
Set-Cookie: csrf_token=xyz789; SameSite=Strict; Path=/
Attribute reference
Expiry
| Attribute | Meaning |
|---|---|
Max-Age=<seconds> |
Cookie lifetime in seconds from now. Takes precedence over Expires. Max-Age=0 deletes the cookie. |
Expires=<date> |
Absolute expiry date (HTTP date format). Older mechanism, use Max-Age when possible. |
| (neither) | Session cookie — deleted when browser closes. |
Scope
| Attribute | Meaning |
|---|---|
Domain=<domain> |
Which hosts the cookie is sent to. Omit for current host only (most secure). Set to .example.com to share across subdomains. |
Path=<path> |
URL path prefix the cookie is scoped to. Default: the path of the Set-Cookie request. Usually set to /. |
Security
| Attribute | Meaning |
|---|---|
Secure |
Cookie only sent over HTTPS. Essential for any auth cookie in production. |
HttpOnly |
Cookie inaccessible to JavaScript (document.cookie). Prevents XSS theft of session cookies. |
SameSite=<value> |
Controls cross-site sending. Options: Strict, Lax (browser default), None (requires Secure). |
Partitioned (newer)
| Attribute | Meaning |
|---|---|
Partitioned |
CHIPS (Cookies Having Independent Partitioned State). Cookie is partitioned by top-level site, preventing cross-site tracking. Required by some browsers for third-party cookies. |
SameSite in depth
SameSite is the most important security attribute after HttpOnly and Secure:
SameSite=Strict
Cookie only sent when the request originates from the same site. Clicking a link from another site to your site will NOT send the cookie on the first request — the user appears logged out until they manually navigate within your site. Maximum CSRF protection, but breaks OAuth redirects and external link flows.
SameSite=Lax (browser default since ~2020)
Cookie sent on same-site requests AND on cross-site top-level navigations (clicking a link). Not sent on cross-site sub-requests (AJAX, images, iframes). Good CSRF protection while allowing normal navigation flows. The right default for most auth cookies.
SameSite=None; Secure
Cookie sent on all requests, including cross-site. Required for third-party cookies (embedded widgets, payment iframes, OAuth flows that use iframes). Must always be paired with Secure. Chrome and Firefox are phasing out support for SameSite=None cookies without Partitioned.
Setting a secure session cookie
The gold standard for auth session cookies:
Set-Cookie: session_id=<token>; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400
HttpOnly— JS can't steal it via XSSSecure— only sent over HTTPSSameSite=Lax— CSRF protectionPath=/— sent on all pathsMax-Age=86400— expires in 24 hours
Deleting a cookie
Send a Set-Cookie with the same name and Max-Age=0 (or an expired Expires):
Set-Cookie: session_id=; Max-Age=0; Path=/; HttpOnly; Secure
The value can be empty. The browser removes the cookie when it sees Max-Age=0.
Domain scoping gotcha
Set-Cookie: token=abc; Domain=example.com
This sets the cookie for example.com and all subdomains (app.example.com, api.example.com, admin.example.com). This is the leading-dot behaviour from RFC 2109 that modern browsers implement.
Omitting Domain entirely scopes the cookie to the exact host only — stricter and more secure for most cases.
How it interacts with the Cookie header
Set-Cookie is the server side; Cookie is the client side. The full roundtrip:
HTTP/1.1 200 OK
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400
→ Browser stores the cookie
GET /dashboard HTTP/1.1
Cookie: session_id=abc123
The browser strips all attributes when sending the Cookie header — only name=value pairs go back to the server.
Common mistakes and gotchas
Missing HttpOnly on session cookies. Without it, any XSS vulnerability on your site can steal session tokens via document.cookie. Always set HttpOnly on auth cookies.
Missing Secure in production. Session cookies without Secure are sent over HTTP too — exposing them to network sniffing. Use Secure unconditionally in production and enforce HTTPS.
SameSite=None without Secure. Modern browsers reject SameSite=None cookies without Secure. They'll be silently dropped.
Setting Domain too broadly. Domain=example.com shares the cookie across all subdomains. If any subdomain is compromised (e.g., a user-controlled subdomain), it can read your auth cookies. Scope to the minimum needed.
Not setting Path=/. If you omit Path, it defaults to the request path — /api/login would scope the cookie to /api/. The session cookie would only be sent on /api/ requests, not /dashboard/. Almost always set Path=/.
Cookie size. Each cookie is limited to ~4096 bytes (name + value + attributes). Storing large JWTs or data blobs in cookies can hit this limit. Store session IDs and keep actual data server-side.
Real-world examples
Login response setting session cookie:
HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: session_id=8f14e45fceea167a5a36dedd4bea2543; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400
{"user": {"id": 42, "name": "Sainesh"}}
Setting multiple cookies:
HTTP/1.1 200 OK
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Lax; Path=/
Set-Cookie: csrf_token=xyz789; Secure; SameSite=Strict; Path=/
Set-Cookie: theme=dark; Max-Age=31536000; Path=/; SameSite=Lax
Logout (deleting cookies):
HTTP/1.1 200 OK
Set-Cookie: session_id=; Max-Age=0; Path=/; HttpOnly; Secure
Set-Cookie: csrf_token=; Max-Age=0; Path=/; Secure
FAQ
What's the difference between Max-Age and Expires?
Max-Age is a relative duration in seconds; Expires is an absolute date. Max-Age takes precedence when both are present. Max-Age is preferred because it doesn't depend on clock synchronisation between client and server. Expires is kept for compatibility with old HTTP/1.0 clients.
Can I set cookies from JavaScript?
Yes — document.cookie = "name=value; path=/" — but only cookies without HttpOnly. JavaScript can't read or set HttpOnly cookies. This is intentional: HttpOnly specifically exists to prevent JS access.
Why does my cookie disappear after the browser closes?
It's a session cookie — no Max-Age or Expires was set. Add Max-Age=<seconds> to make it persistent across browser restarts.
What's the Partitioned attribute for?
Partitioned (CHIPS) is a response to third-party cookie deprecation. Without partitioning, a cookie set by an embedded widget on site-a.com is also sent when the same widget loads on site-b.com — enabling cross-site tracking. With Partitioned, the cookie is keyed to the top-level site, so site-a.com's cookie isn't sent when the widget loads on site-b.com. Chrome requires Partitioned for third-party cookies in its Privacy Sandbox rollout.
Fun fact
The Set-Cookie header is the reason your browser can remember you're logged in. Without cookies, every HTTP request would be completely anonymous — you'd have to log in on every single page navigation. Lou Montulli's 1994 invention of cookies (originally for Netscape's shopping cart) fundamentally changed the web from a stateless document system into the interactive, authenticated, personalised platform it is today. The spec wasn't formalised until RFC 2109 in 1997 — three years after cookies were already in production on millions of websites.