Cookie Security: Secure, HttpOnly and SameSite
Cookies often carry the most valuable thing on your site: the session that keeps a visitor logged
in. Anyone who obtains a session cookie can act as that user. Three attributes on the Set-Cookie
header decide how well that cookie is protected.
Why it matters
| Attribute | Protects against |
|---|---|
Secure |
the cookie being sent over plain HTTP, where anyone on the network path can read it |
HttpOnly |
JavaScript reading the cookie, so a cross-site scripting bug cannot simply copy the session out |
SameSite |
the browser attaching the cookie to requests started by other sites, the basis of cross-site request forgery (CSRF) |
None of these replace other defenses: you still need HTTPS everywhere, output escaping and CSRF protection for state-changing requests. They limit the damage when something else goes wrong.
What we check
We look at every Set-Cookie header your server sends when we load the scanned page.
| Check | Passes when |
|---|---|
| Cookie flags | every cookie has Secure (on HTTPS sites), HttpOnly and a SameSite attribute |
- A cookie without
Secureon an HTTPS site fails the check. - A cookie missing only
HttpOnlyorSameSiteis reported as “needs attention”. - A page that sets no cookies passes.
The report lists each affected cookie by name and the attributes it is missing. We only see cookies set in that one response. Cookies set later, by JavaScript or by other pages such as the login form, are not included.
How to fix
For a session cookie, the target looks like this:
Set-Cookie: session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax
Choosing SameSite
Lax: the cookie is sent on normal top-level navigation from other sites (following a link) but not on cross-site form posts, frames or background requests. A good default for session cookies.Strict: never sent on requests started by another site, not even links. Visitors arriving from an email or search result will appear logged out on the first page. Good for high-value actions on a separate cookie.None: always sent, for cookies that must work in cross-site contexts such as embedded widgets or third-party sign-in. Browsers requireSecuretogether withSameSite=None.
Some browsers treat a cookie without SameSite as Lax, but not all do. Set it explicitly.
Use the __Host- prefix for session cookies. A cookie named __Host-session is only accepted
by browsers if it has Secure, Path=/ and no Domain attribute. That stops subdomains from
overwriting it.
Set-Cookie: __Host-session=abc123; Path=/; Secure; HttpOnly; SameSite=Lax
In your application
Set the attributes where the cookie is created. Examples:
Node.js (Express)
res.cookie('session', value, { secure: true, httpOnly: true, sameSite: 'lax' });
PHP (php.ini, for the built-in session cookie)
session.cookie_secure = 1
session.cookie_httponly = 1
session.cookie_samesite = "Lax"
Django (settings.py)
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
CSRF_COOKIE_SECURE = True
At the proxy
If you cannot change the application, Nginx 1.19.3 and newer can add flags to cookies from a proxied backend:
proxy_cookie_flags ~ secure httponly samesite=lax;
Fixing it in the application is better, because the application knows which cookies need which settings.
Cookies that need JavaScript access
Some cookies are meant to be read by your own scripts, for example a consent choice or a CSRF token
that a script copies into a request header. Those cannot be HttpOnly, and our check will list
them. That is expected: make sure they contain nothing sensitive, keep Secure and SameSite on
them, and never store a session identifier in a cookie scripts can read.
Third-party cookies
Cookies used by analytics or ad services are set by their scripts or their own servers, not by your page response, so this check does not see them. Review them through your consent tool and the vendor’s documentation.
Further reading: MDN: Using HTTP cookies and OWASP: Session Management Cheat Sheet.