When a user signs in to a website, in most cases the server hands them a session cookie: an identifier the browser sends back with every subsequent request to prove "it's me, I've already logged in". Whoever gets hold of that value can pass themselves off as the user. That is why the attributes a cookie is created with are not details: they decide who can read it, when it is sent and how long it stays valid.
How a cookie is born
The server sets a cookie with the Set-Cookie header:
Set-Cookie: session=a8F3kq92Lm; Path=/; Secure; HttpOnly; SameSite=Lax
Before the semicolon come the name and value; after it, the attributes. The browser stores the cookie and automatically attaches it to subsequent requests that meet the stated conditions.
Secure
Set-Cookie: session=...; Secure
The cookie is sent only over HTTPS connections. Without this attribute, a single unencrypted request, for example caused by a forgotten http:// link, would expose the value to anyone able to observe the traffic. For session cookies it is essential.
HttpOnly
Set-Cookie: session=...; HttpOnly
The cookie is not accessible from JavaScript: it does not appear in document.cookie. If an attacker manages to inject a script into the page through a cross-site scripting vulnerability, they will not be able to read the session cookie and take it elsewhere.
It is not complete protection against XSS, because the injected script can still perform actions in the page on the user's behalf, but it prevents the session from being stolen for later reuse. Session cookies should always be HttpOnly; only cookies your JavaScript genuinely needs to read, such as a theme preference, can do without it.
SameSite
This attribute controls whether the cookie is sent with requests that originate from other sites. It is the main modern defence against CSRF (Cross-Site Request Forgery), an attack in which a malicious page makes the user's browser send a request to your site, cookies included.
| Value | Behaviour |
|---|---|
Strict |
the cookie is never sent in requests originating from other sites, not even when following a link |
Lax |
sent when the user follows a link to your site, but not with POST requests, iframes or loads originating from other sites |
None |
always sent; it also requires Secure |
Lax is a good balance for most session cookies: users arriving from a link stay signed in, while forms submitted from other sites do not carry the cookie. Many browsers already apply Lax when the attribute is missing, but it is better to declare it explicitly, because the default behaviour is not identical everywhere.
Strict is more secure but can be surprising: anyone clicking a link to your site from an email or another site arrives as if not signed in.
Remember that SameSite greatly reduces CSRF risk but does not fully replace anti-CSRF tokens for critical operations, not least because it treats subdomains of the same domain as the "same site".
Domain and Path
- Without
Domain, the cookie is sent only to the exact host that set it. This is the most restrictive choice and usually the best. - With
Domain=example.com, the cookie is also sent to every subdomain. If one of them is run by a third party or is less well protected, the cookie becomes exposed. Pathlimits the paths the cookie is sent to, but it is not a security boundary: pages on the same host can still access other paths' cookies in various ways. Do not rely onPathto isolate sensitive data.
Expiry
- With neither
ExpiresnorMax-Age, the cookie is a browser-session cookie: it is deleted when the browser considers the session closed (in practice, many browsers restore sessions and these cookies can survive for a long time). Max-Age=3600makes it expire after one hour;Expiresspecifies a precise date.
Expiry in the browser is not enough: the session must also expire on the server, which should reject identifiers that are too old or inactive.
The __Host- prefix
If the cookie's name starts with __Host-, the browser accepts it only if:
- it has the
Secureattribute; - it was set by an HTTPS page;
- it has no
Domainattribute; - it has
Path=/.
Set-Cookie: __Host-session=a8F3kq92Lm; Path=/; Secure; HttpOnly; SameSite=Lax
This way a compromised subdomain cannot overwrite the main domain's session cookie. There is also the __Secure- prefix, which only requires Secure.
Good practice for session management
- Generate long, random session identifiers with a secure generator, never predictable or sequential ones.
- Regenerate the identifier after sign-in: a value set before login then cannot be reused by anyone who knew it.
- Invalidate the session on the server at logout; do not just delete the cookie.
- Do not store sensitive data in plain text in cookies: the cookie holds an identifier, the data stays on the server.
- Remember size limits: a cookie should stay within about 4 KB, and all of a domain's cookies travel with every request, making it heavier.
A recommended configuration
For the session cookie of a web application served on a single host:
Set-Cookie: __Host-sid=<random value>; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=28800
In short
Securesends the cookie only over HTTPS;HttpOnlyhides it from JavaScript.SameSite=Laxis a good default against CSRF;NonerequiresSecure.- Avoid
Domainunless you need it, and do not treatPathas a security boundary. - The
__Host-prefix prevents subdomains from overwriting the cookie. - Regenerate the session after sign-in and make it expire on the server too.