Cookie security flags explained: Secure, HttpOnly, SameSite
// published 2026-04-17
Three small attributes — Secure, HttpOnly, SameSite — decide whether a cookie is a session token a careful engineer would deploy or a credential leak waiting to happen. Here's what each one does, when to use it, and what attacks it stops.
The default cookie is dangerous
By default, when a browser receives Set-Cookie: session=abc with no flags, it stores the cookie and:
- Sends it on every request to that domain (over HTTP or HTTPS)
- Makes it readable from JavaScript via
document.cookie - Sends it on cross-site requests (e.g. when an attacker's site triggers a request to your site)
Each of those is exploitable. The flags fix them one at a time.
Secure
Set-Cookie: session=abc; Secure
Browser only sends the cookie over HTTPS. Even if a network attacker can intercept HTTP requests to your domain, the cookie won't be transmitted in cleartext.
When to set: always, on every cookie. There is no scenario in 2026 where you want a cookie sent over HTTP.
What it stops: network-level credential theft on hostile WiFi, transparent proxies, MITM attackers.
HttpOnly
Set-Cookie: session=abc; Secure; HttpOnly
JavaScript can't read or modify the cookie via document.cookie. Only the browser (sending it on HTTP requests) and the server (receiving it) ever see the value.
When to set: always for session cookies, auth tokens, CSRF tokens. Skip only for cookies that legitimate JavaScript needs to read (rare — usually you should re-architect to avoid that).
What it stops: XSS-based session theft. An attacker who manages to inject JS into your page can do many bad things, but stealing the session cookie via document.cookie isn't one of them.
SameSite
Set-Cookie: session=abc; Secure; HttpOnly; SameSite=Lax
Controls when the cookie is sent on cross-site requests. Three values:
- Strict — never sent on cross-site requests. Hardest setting. Breaks legitimate flows where users follow a link from another site (e.g. an email link to your app — they'd arrive logged out).
- Lax — sent on top-level cross-site GETs (link clicks) but not on cross-site POSTs, iframes, or background fetches. Reasonable default for most apps; doesn't break the "click a link in email" UX. Browser default since Chrome 80.
- None — sent on all cross-site requests. Required for cookies that need to work in iframes or third-party contexts (e.g. embeddable widgets, OAuth popups). Must be paired with
Secure.
When to use what:
- Session cookie for a SaaS app:
SameSite=Lax - Banking, admin:
SameSite=Strict - Embedded widget that needs cross-site auth:
SameSite=None; Secure
What it stops: CSRF attacks. Without SameSite, an attacker's site can trick a logged-in user's browser into making an authenticated request to your site (transferring funds, changing email, etc.) and the browser will dutifully include the session cookie.
The minimum viable session cookie in 2026
Set-Cookie: session=...; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=86400
Plus rotation on login (Max-Age resets) and a server-side invalidation list for logout (cookie deletion alone isn't enough — server must also reject the value).
Common mistakes
- Setting
SameSite=NonewithoutSecure. Browsers reject the cookie entirely. - Forgetting
HttpOnlyon session cookies "because the JavaScript framework reads them". Re-architect. The framework can call a server endpoint to learn the user identity. - Mixing flagged and unflagged cookies on the same domain. Browsers honor each cookie's flags independently, but this often signals oversight elsewhere — audit the whole set.
- Using
Secureon localhost. Browsers usually allow this forlocalhostas a special case in dev, but it can confuse new developers. UseSecureconditionally based on environment if you must.
Run the Cookie Audit on your own pages to see which flags are missing.