Frontend Labs
⋯

Chapter 41 — Cookies: how the web remembers you

In one sentence

HTTP is stateless, so the server hands the browser a small piece of text and the browser sends it back on every matching request — automatically, which is both the feature and the danger.

How they work

   Server -> browser:
     Set-Cookie: session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=3600

   Browser -> server, on EVERY matching request from then on:
     Cookie: session=abc123

The word automatically is the important one. The browser attaches cookies based on scope, without the page asking. That's what makes sessions work — and what makes CSRF possible.

The attributes, and what each actually protects

Attribute Effect Why you want it
Secure HTTPS only Stops the cookie being sent in the clear. Always set it.
HttpOnly JavaScript can't read it If your site has an XSS bug, the attacker still can't steal the session token
SameSite=Lax Not sent on cross-site requests, except top-level navigation Blocks most CSRF. This is the modern browser default.
SameSite=Strict Never sent cross-site at all Stronger, but breaks "click the link in the email and be logged in"
SameSite=None Sent everywhere — requires Secure Only for genuine third-party use (embeds, SSO)
Domain=example.com Sent to that domain and all its subdomains Omitting Domain is narrower and safer
Path=/app Only for URLs under that path Convenience, not security
Max-Age / Expires Survives browser restart Without either, it's gone when the browser closes
__Host- name prefix Browser enforces Secure, Path=/, and no Domain The strongest available binding

The trap: cookies are not origin-scoped

Everything else on the web is scoped to an origin (scheme://host:port). Cookies are not:

   Set by:  http://example.com:8080
   Sent to: https://example.com:443     <-- yes, really.

   Cookies ignore the PORT, and (mostly) the SCHEME.

And the Domain attribute is worse:

   Set-Cookie: session=abc; Domain=example.com

   -> sent to  app.example.com        (intended)
   -> sent to  blog.example.com       (probably fine)
   -> sent to  promo.example.com      (run by an agency... with an XSS bug)

Rule: don't set Domain unless you truly need subdomain sharing, and never host untrusted content on a subdomain of your main site.

CSRF, and why SameSite changed everything

   You are logged into your bank (cookie stored).
   You visit evil.com, which contains:

       <form action="https://bank.com/transfer" method="POST">
         <input name="to" value="attacker"><input name="amount" value="1000">
       </form>
       <script>document.forms[0].submit()</script>

   The browser HELPFULLY attaches your bank cookie. The bank sees a
   perfectly authenticated request.

Defences, strongest first:

  1. SameSite=Lax or Strict — the browser simply doesn't send the cookie. Now the default, which killed most classic CSRF overnight.
  2. Anti-CSRF token — a random value in the form, verified server-side. Still needed for SameSite=None flows.
  3. Check the Origin header server-side on state-changing requests.
  4. Use Authorization: Bearer instead of cookies — headers aren't attached automatically, so CSRF doesn't apply. (But you then have to store the token somewhere, and localStorage is readable by XSS. Pick your trade-off.)

Practice (20 min)

A. See cookies being set:

curl -sI https://github.com | grep -i set-cookie
curl -sI https://example.com | grep -i set-cookie

B. Inspect them properly in the browser: DevTools → Application → Cookies. The columns are literally the attributes above. Look at a site you're logged into: is the session cookie Secure? HttpOnly? What's its SameSite?

C. Watch a cookie round-trip:

curl -s -c /tmp/jar.txt https://httpbin.org/cookies/set?flavour=chocolate > /dev/null
cat /tmp/jar.txt                                        # the stored cookie
curl -s -b /tmp/jar.txt https://httpbin.org/cookies     # sent back automatically

D. Measure the cost. Cookies are sent on every request to that scope — including images and API calls. Check the size:

curl -sI https://<a site you use> | grep -i set-cookie | wc -c

Multiply by every request on a page load. Large cookies are a real performance problem, which is why static assets are often served from a cookie-less domain.

Common confusions

  • "Cookies are insecure." Cookies with Secure, HttpOnly, and SameSite are a solid session mechanism — often safer than tokens in localStorage, which JavaScript (and therefore XSS) can read.
  • "Path protects my cookie." It doesn't. JavaScript on the same origin can read cookies across paths.
  • "Third-party cookies are being removed — does that break sessions?" No. Your own site's cookies (first-party) are unaffected. It's cross-site tracking cookies that are going away.

Check yourself

  1. Why does the browser send cookies automatically, and what problem does that create?
  2. What does HttpOnly protect against? What about SameSite?
  3. Why is omitting the Domain attribute safer than setting it?
  4. Name two defences against CSRF.

Further reading

  • Docs: MDN, "Using HTTP cookies" and "Set-Cookie" — accurate and complete.
  • Article: "SameSite cookies explained" — web.dev. Clear, with diagrams.
  • Article: OWASP "Cross-Site Request Forgery Prevention Cheat Sheet".
  • Labs: PortSwigger Web Security Academy — free CSRF labs you can actually exploit.

Generated from real-life-guide-v1.md · 8 Oct 2026 16:36