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.
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
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.
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.
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:
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: truesecure: 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
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
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
None — only with a real reason.
Allows the cookie cross-site — needed for legitimate embeds or some SSO flows. If you setSameSite=Noneyou must also setSecure, 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.comif 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.