Secure Cookie Settings: HttpOnly, Secure and SameSite
Practice4 minInnovialabs editorial team
In short
A session cookie should be HttpOnly, Secure, SameSite=Lax (or Strict), use the __Host- prefix and have a server-enforced lifetime. Together these flags block theft via XSS, leaks over HTTP and most CSRF.
The short answer
For the session cookie of most web applications, this set works well:
Set-Cookie: __Host-session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=86400
Each attribute closes a specific threat, and dropping any of them opens a specific hole.
What each flag does
| Flag | What it does | What it protects against |
|---|---|---|
HttpOnly | cookie is not readable from JavaScript (document.cookie) | session theft via XSS |
Secure | cookie is sent only over HTTPS | interception on open networks, accidental HTTP visits |
SameSite | limits sending with cross-site requests | CSRF, some cross-site leaks |
Domain | extends the cookie to subdomains | nothing (it widens the attack surface) |
Path | limits the path | not a security boundary |
Max-Age / Expires | lifetime in the browser | long-lived stolen sessions |
HttpOnly does not stop XSS itself: a malicious script can still send requests as the user. But it cannot carry the cookie away and reuse it from another device.
Domain is best left out entirely. Without it the cookie is host-only and is sent only to the exact domain that set it.
Lax, Strict and None
- Strict — the cookie is never sent with cross-site requests, not even when clicking a link from email or a messenger. A user arriving via an external link appears logged out on the first page.
- Lax — the cookie is sent on normal top-level navigation (
GET), but not with cross-sitePOST,fetch,iframeor image requests. A sensible default. - None — the cookie is always sent. Needed for widgets inside third-party
iframes and some cross-domain setups. Must be paired withSecureor the browser rejects it.
If SameSite is not set, browsers differ: some apply Lax by default, some do not. Always set it explicitly.
A common compromise is two cookies: a Strict one for dangerous actions (password change, payments) and a Lax one for normal navigation.
Cookie prefixes
The browser enforces name prefixes and refuses cookies that break their rules:
__Secure-— the cookie must haveSecureand be set from an HTTPS page.__Host-— the same, plusPath=/and noDomain. Such a cookie cannot be overwritten from a subdomain.
For sessions, __Host- is the best choice unless you need shared login across subdomains.
Session lifetime
- A cookie without
Max-AgeorExpiresis a session cookie, but browsers that restore tabs may keep it for days. Do not rely on "closed the browser, logged out". - The real lifetime is enforced on the server: an idle timeout (session dies after inactivity) and an absolute timeout (even an active session ends).
- On login and privilege changes, issue a new session ID — this closes session fixation.
- On logout, destroy the session on the server, not just the cookie in the browser.
- Admin panels and financial sections deserve shorter lifetimes than a regular account area.
Mistakes that break login or leak sessions
Secureon a local HTTP environment with a custom hostname — the cookie is not stored and login silently fails. Use HTTPS locally orlocalhost.SameSite=NonewithoutSecure— the browser drops the cookie.SameSite=Strictwith OAuth or payment redirects: the provider sends the user back via a cross-site request and session state is lost. Returns viaPOSTbreak most often — they do not get the cookie even withLax.Domain=.example.comwithout a reason — every subdomain sees the cookie, including staging and third-party services.- Session tokens in
localStorage— readable by any script on the page. - Same cookie name on different subdomains or paths — the browser sends several values and the server picks the wrong one.
- Endless "remember me" tokens that are not revoked on password change.
Attribute reference: MDN Set-Cookie.