Skip to content
Blog
Playbook

Your session cookie is stealable

Login works, so cookies feel like a solved problem. But a session cookie missing three small flags is one bug away from being copied — and a copied session means someone is logged in as your user, with no password required.

The buckingfugs crewJuly 6, 20265 min read

When someone logs in, your server hands the browser a cookie that effectively says “this is user 4821, already authenticated.” The browser sends it back on every request so people don't have to re-type their password on every click. The catch: whoever holds that cookie value isthat session. Three flags decide how hard it is for anyone else to get their hands on it — and they're off unless you turn them on.

The three flags that matter

1

HttpOnly — hide it from JavaScript

Without this, any script running on your page can read document.cookie and ship your session off to someone else. That's the whole payoff of a cross-site scripting (XSS) bug or one sketchy third-party script. With HttpOnly set, the cookie is invisible to JavaScript entirely — even an injected script can't read it. This is your seatbelt for the day something slips through.

2

Secure — HTTPS only

Without Secure, the browser will send the cookie over a plain http:// connection — an old link, a downgraded request — where anyone on the network can read it in the clear. With it, the cookie only ever travels encrypted. It pairs naturally with HSTS, which forces the HTTPS in the first place.

3

SameSite — control cross-site tag-alongs

This decides whether your cookie rides along when another site makes a request to yours. Left unmanaged, that's the mechanism behind cross-site request forgery (CSRF) — a malicious page quietly firing authenticated actions using your logged-in session. SameSite shuts that down, and it's worth getting the value right (next section).

The copy-paste version

On the wire, a well-dressed session cookie looks like this — every flag present:

response header
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400

You almost never write that header by hand, though. Set it wherever your framework or session library configures cookies — the option names are the same everywhere:

  • httpOnly: true
  • secure: true (in production — many setups leave it off on localhost, which has no HTTPS)
  • sameSite: 'lax'

Getting SameSite right without locking yourself out

This is the one flag where the wrong choice is annoying in both directions — too loose and you're exposed, too strict and you log your own users out. Three values:

  1. 1

    Lax — the right default.

    The cookie rides along on normal top-level navigation (someone clicking a link to your site stays logged in) but is withheld on cross-site subrequests, which kills the standard CSRF path. This is the correct answer for almost every app.
  2. 2

    Strict — when the stakes are high.

    The cookie is withheld even on top-level navigation from another site. Great for sensitive admin surfaces — with one UX gotcha: a user who clicks a link to you from their email lands logged outon the first hit, because the cookie didn't come along. Worth it sometimes, surprising always.
  3. 3

    None — only with a real reason.

    Allows the cookie cross-site — needed for legitimate embeds or some SSO flows. If you set SameSite=None you must also set Secure, or modern browsers reject the cookie outright. Don't reach for this unless something genuinely breaks without it.

While you're in there

Two neighbors worth a glance in the same pass:

  • Don't over-share the scope. Only set Domain=.yourdomain.com if the cookie truly needs to work across subdomains. A broad scope means a hostile or taken-over subdomain can reach your session cookie too.
  • HttpOnly is a backstop, not the cure. It limits the damage of XSS, but the real fix for XSS is a Content-Security-Policy. Set both.

None of this is a rewrite — it's three flags on the cookie you already issue, plus a moment's thought about scope. Get them right and stealing a session stops being a one-line document.cookie away. Get them wrong and it's exactly that easy.

The scan flags every cookie that's dressed wrong — missing HttpOnly, Secure, or SameSite. Takes about 20 seconds.

Scan your site free →