Cookies, SameSite, and Session Transport
LESSON
Cookies, SameSite, and Session Transport
The core idea: Cookies are ambient request state: a server sets them once, then the browser automatically attaches matching cookies to later requests, so the cookie attributes are part of the security and delivery contract.
Core Insight
Imagine the checkout system after the caching lesson. A user signs in at https://www.shop.test, then opens the checkout page. The browser sends a request to https://api.shop.test/cart/summary, and the request includes a session cookie. The application code sees an authenticated user. The user did not manually copy a token into the request. The browser attached it because earlier the server told the browser to store a cookie.
That automatic behavior is why cookies are convenient and dangerous. They preserve continuity across pages, reloads, redirects, and browser tabs. They also create ambient authority: if a request matches the cookie's scope, the browser may attach the credential even when the request was triggered by an image tag, a form submission, a redirect, or a script on another site. A cookie does not prove that the user intentionally performed this particular action. It proves that the browser has matching stored state.
The central mechanism is the browser's cookie jar. Set-Cookie creates or updates a stored record. Attributes such as Domain, Path, Secure, HttpOnly, SameSite, and expiration limit when that record is sent or exposed. Later, before a request leaves the browser, the cookie jar compares the request URL and context against those attributes. If the rules match, the browser adds a Cookie header.
The trade-off is user continuity versus cross-site abuse surface. Cookies make sessions ergonomic because every request does not need explicit credential plumbing. But the same automatic transport can help cross-site request forgery, subdomain leakage, accidental shared-cache mistakes, and confusing login behavior. Good cookie design keeps the convenience while narrowing where the browser is allowed to carry the credential.
What Set-Cookie Actually Creates
A login response might include:
HTTP/1.1 204 No Content
Set-Cookie: __Host-shop_session=s_abc123; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=3600
Cache-Control: no-store
The body is not the interesting part. The Set-Cookie field asks the browser to store a cookie named __Host-shop_session with value s_abc123 and several attributes.
Secure says the cookie should only be sent over HTTPS. That protects the cookie from being sent over cleartext HTTP, where it could be observed or modified on the network.
HttpOnly says JavaScript should not be able to read the cookie through document.cookie. This helps when a page has an injection bug: injected script may still act as the user while it runs, but it cannot simply copy the session cookie value and send it away through normal cookie APIs.
Path=/ says the cookie applies to requests under the whole host path tree. Path is a routing filter, not a strong security boundary. Do not rely on /admin versus /checkout paths to isolate hostile code.
No Domain attribute means the cookie is host-only: it belongs to the exact host that set it. The __Host- prefix reinforces this shape in supporting browsers: host-only, Secure, and Path=/. That is often a good default for session cookies because a compromised sibling subdomain should not automatically receive the main session credential.
SameSite=Lax limits when the browser sends the cookie on cross-site requests. It still preserves common navigation flows better than Strict, while blocking many cross-site unsafe submissions. SameSite=None allows cross-site sending, but modern browsers require it to be paired with Secure. Use it only when a cross-site embedded or federated flow truly needs it.
Max-Age=3600 gives the cookie a lifetime. Session cookies without an explicit lifetime usually disappear when the browser session ends, subject to browser behavior. Long-lived cookies improve convenience, but they increase the window in which a stolen or misused credential remains useful.
The Sending Decision
The browser does not send every cookie on every request. It filters.
Suppose the stored cookie is:
name = __Host-shop_session
host = www.shop.test
path = /
secure = true
httpOnly = true
sameSite = Lax
expires = one hour from login
Now compare four requests:
1. https://www.shop.test/checkout
2. http://www.shop.test/checkout
3. https://api.shop.test/cart/summary
4. https://evil.example/pay-now-form -> submits to https://www.shop.test/orders
The first request matches the host, path, HTTPS requirement, and site context, so the cookie can be sent.
The second request is HTTP, not HTTPS, so Secure blocks the cookie.
The third request is api.shop.test, not www.shop.test. A host-only cookie set by www.shop.test does not automatically go to api.shop.test. If the system needs both hosts to share session state, it must design that deliberately, often by using a parent Domain attribute or by using separate tokens and backend session exchange. Broadening the domain increases the blast radius of a compromised subdomain.
The fourth request is the cross-site risk. The user may be logged in to www.shop.test, but the request was initiated from evil.example. Depending on method, navigation type, and SameSite, the browser may or may not attach the cookie. The point is not to memorize every browser edge case. The point is to see that the browser is making a transport decision before the server sees the request.
This is the request-side mechanism:
stored cookie -> request URL + context -> attribute checks -> Cookie header or no Cookie header
Server code receives only the result. If the Cookie header is present, the server still must check session validity, authorization, CSRF protections for unsafe actions, and cache policy.
SameSite Is About Sites, Not API Intent
SameSite is often misunderstood because the name sounds like application authorization. It is not. It is a browser rule about whether a cookie should be sent when the request is same-site or cross-site.
A site is based on the registrable domain and scheme in modern browser behavior. https://www.shop.test and https://api.shop.test may be same-site even though they are different origins. They are not the same origin because the host differs, but they can still be same-site because they share the same site. This distinction matters: cookies and CORS do not answer the same question.
SameSite=Strict is the narrowest common setting. It withholds the cookie on cross-site navigations. This is strong against cross-site request forgery, but it can break flows where a user follows a link from another site and expects to remain signed in immediately.
SameSite=Lax is a pragmatic default for many session cookies. It allows more normal top-level navigation behavior while blocking many cross-site unsafe requests. It is not a full application security policy. Sensitive state-changing operations should still use CSRF tokens or equivalent request-specific proof of user intent.
SameSite=None is for cookies that must be sent in cross-site contexts, such as some embedded widgets or federated login flows. It should be a deliberate exception because it restores the broadest cross-site sending behavior.
The boundary sentence is: SameSite controls whether the browser transports the cookie; it does not decide whether the server should honor the request.
Worked Path: Login, Checkout, Cross-Site Form
Trace one session:
t0: user submits credentials to https://www.shop.test/login
t1: server validates credentials and returns Set-Cookie
t2: browser stores __Host-shop_session with Secure, HttpOnly, SameSite=Lax
t3: user opens https://www.shop.test/checkout
t4: browser attaches Cookie: __Host-shop_session=s_abc123
t5: server loads session, authorizes checkout view, returns private/no-store responses
That is the useful path. The user stays signed in, the server does not put the session ID in URLs, and the browser handles repeated credential transport.
Now add a hostile page:
<form action="https://www.shop.test/orders/cancel" method="POST">
<input name="order_id" value="842">
</form>
If the user is logged in and visits the hostile page, the browser may be asked to submit a request to www.shop.test. Without SameSite and CSRF protection, the session cookie could ride along automatically, and the server might see an authenticated request that the user did not intend.
With SameSite=Lax, many cross-site unsafe submissions are blocked from carrying the cookie. With SameSite=Strict, the cookie is withheld even more aggressively. With a CSRF token, the server also requires a request-specific value that the hostile page cannot guess. The robust design uses layers: cookie attributes reduce ambient transport, and server-side CSRF checks protect sensitive mutations.
This is why HttpOnly alone is not enough. HttpOnly helps prevent script from reading the cookie value. It does not stop the browser from sending the cookie on a matching request. A CSRF attack does not need to read the cookie; it tries to make the browser carry it.
Operational Failure Modes
Failure: setting Domain=.shop.test because it feels convenient. This shares the cookie across subdomains. If blog.shop.test or preview.shop.test is less protected than www.shop.test, it can become part of the session threat model. Prefer host-only session cookies unless cross-subdomain sharing is truly required.
Failure: using SameSite=None for every login problem. This may fix an integration flow, but it reopens broad cross-site credential transport. First identify whether the issue is same-site versus cross-site, embedded versus top-level navigation, or a CORS credentials configuration problem for the next lesson.
Failure: assuming Path isolates sensitive cookies. Path limits when a cookie is sent, but it is not a reliable boundary between mutually distrusting applications on the same host. Separate sensitive applications by host when the security boundary matters.
Failure: cache and cookie contracts disagree. A response that sets or depends on a session cookie should not be treated as public shared-cache content. The previous lesson's cache rules still apply: personalized session responses need private, no-store, or similarly deliberate policy.
Useful signals include Set-Cookie audits, login-loop reports after changing Secure or Domain, CSRF rejection rates, unexpected cross-site Origin or Referer patterns on unsafe methods, and session cookies appearing on hosts that should not receive them. Cookie bugs often look like authentication bugs at first, but the root cause is usually scope or context.
Close the lesson and inspect one session cookie from a real application in browser devtools. Identify its host or domain, path, Secure, HttpOnly, SameSite, and lifetime. Then decide which requests should carry it, which requests should not, and what server-side check protects unsafe actions even when the cookie is present.
Connections
The caching lesson showed why personalized responses must not be casually stored by shared caches. Cookies explain one reason: the same URL may produce different responses depending on automatic browser credential state.
The next lesson separates cookie transport from CORS. Cookies decide whether credentials are attached to a request. CORS decides whether browser JavaScript is allowed to read a cross-origin response. Those are different gates, and confusing them produces some of the most frustrating web API failures.
Resources
- [RFC] HTTP State Management Mechanism RFC 6265
- Focus: Use it for the baseline cookie storage and sending model.
- [DOC] MDN: Set-Cookie
- Focus: Review
Secure,HttpOnly,SameSite,Domain,Path, expiration, and cookie prefixes.
- Focus: Review
- [DOC] web.dev: SameSite cookies explained
- Focus: Use it for practical browser behavior and why
SameSite=Nonemust be paired withSecure.
- Focus: Use it for practical browser behavior and why
- [CHEATSHEET] OWASP Cross-Site Request Forgery Prevention
- Focus: Connect cookie transport to server-side CSRF defenses for unsafe actions.
Key Takeaways
- Cookies are automatic browser-managed request state, not explicit proof that the user intended a specific action.
Secure,HttpOnly,Domain,Path, expiration, and prefixes narrow where a cookie is sent or exposed.SameSitereduces cross-site credential transport, but sensitive mutations still need server-side CSRF protection.- Cookie scope, cache policy, and browser security rules must agree; otherwise session bugs become privacy, security, or debugging incidents.