Back to HTTP Headers

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 XSS
  • Secure — only sent over HTTPS
  • SameSite=Lax — CSRF protection
  • Path=/ — sent on all paths
  • Max-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.

Related Headers