Cookie security: the Secure, HttpOnly and SameSite flags explained and configured
Session cookies without Secure, HttpOnly or SameSite can be stolen or misused. How to check each flag and set it correctly.
Typical severity: Medium · Checked by NovaPentest module: Cookie Security
What it is
Cookies carry the state that keeps a user logged in: session identifiers, authentication tokens, CSRF tokens, preferences. Whoever holds a valid session cookie is, as far as your application can tell, that user. The Set-Cookie header lets the server attach attributes that limit where and how the browser will use each cookie.
Secure tells the browser to send the cookie only over HTTPS. Without it, the cookie is included in any plain HTTP request to your domain, including one triggered by an old link or an image tag.
HttpOnly hides the cookie from JavaScript. The browser still sends it with requests, but document.cookie cannot read it, so a script that ends up running in your page cannot copy the value.
SameSite controls whether the cookie is sent on requests that start from another site. Lax, which modern browsers apply when the attribute is absent, withholds the cookie on cross-site subrequests and form posts but allows it on top-level navigation by link. Strict withholds it on every cross-site request. None sends it everywhere and requires Secure.
Lifetime matters too. A cookie with no Expires or Max-Age is a session cookie and disappears when the browser closes; one with a long expiry stays valid on the device for as long as you allow.
Together these attributes turn a cookie from a value that any page, network or script can reach into one that only travels over an encrypted channel to your own site.
How it is exploited
A session cookie without Secure can leave the browser in clear text. If a user on shared Wi-Fi follows any http:// link to your domain, even one that immediately redirects to HTTPS, that first request carries the cookie, and anyone able to observe the network can reuse it to act as the user until the session ends.
A session cookie without HttpOnly turns any script injection into account takeover. If a cross-site scripting bug exists anywhere on the domain, or a third-party script you include is compromised, the script can read the cookie and send it elsewhere. With HttpOnly the injected code can still misbehave inside the page, but it cannot take the session with it.
Without an explicit SameSite, you depend on browser defaults, which differ across browsers and versions. A cookie that ends up behaving as SameSite=None is sent on requests forged by another site, so a page the user visits elsewhere can submit a form to your application with the user's session attached, changing settings or triggering actions they never intended.
Long-lived cookies extend every one of these risks in time. A session token valid for years, stored on a shared or lost laptop, remains a working credential long after the user has forgotten about it.
In each case the user does nothing wrong and sees nothing unusual; the damage lands on their account and their data.
How to detect it
NovaPentest's Cookie Security module makes one GET request to the target URL, follows redirects, and parses every Set-Cookie header in the final response. For each cookie it reports a missing Secure attribute as Medium, a missing HttpOnly attribute as Medium, and a missing SameSite attribute as Low. It also flags as Low any cookie whose Max-Age, or failing that its Expires date, lies more than one year in the future. The module runs in the free passive scanner at novapentest.com/scan.
The check has clear limits. It sees only cookies set on the landing page for an anonymous visitor; session cookies issued after login, or cookies set on intermediate redirect responses, are not examined. It does not know which cookies are sensitive, so it flags missing HttpOnly on cookies that legitimately need JavaScript access, such as some consent or analytics cookies, and those findings can be accepted. It checks only that SameSite is present, so SameSite=None passes. If the page sets no cookies, it reports nothing.
To verify by hand, run curl -sI https://example.com and read each Set-Cookie line; add -L to follow redirects. Repeat after logging in by passing your session with -b, or better, open the browser's developer tools, go to Application (Chrome) or Storage (Firefox), and inspect the cookie table, which shows Secure, HttpOnly, SameSite and expiry for every cookie, including those created by JavaScript. A correct session cookie looks like Set-Cookie: sid=...; Path=/; Secure; HttpOnly; SameSite=Lax, with no Expires or a short one.
How to fix it
1. Inventory the cookies your site sets and classify them. Session and authentication cookies, CSRF tokens and anything that identifies a user are sensitive. Fix those first.
2. Set Secure on every cookie of an HTTPS site; there is rarely a reason not to. Serve the whole site over HTTPS and redirect HTTP, so no request carries cookies in clear text.
3. Set HttpOnly on every cookie that JavaScript does not need to read. Session cookies should never need it.
4. Set SameSite explicitly. Lax is the right default for session cookies because users still arrive logged in from links. Use Strict for cookies that guard sensitive actions, and SameSite=None; Secure only for cookies that must work inside a third-party iframe or a cross-site flow.
5. Keep sessions short and also expire them server-side. The target header looks like Set-Cookie: sid=...; Path=/; Secure; HttpOnly; SameSite=Lax.
6. Configure the framework rather than writing headers by hand. In ASP.NET Core, set options.Cookie.SecurePolicy = CookieSecurePolicy.Always, options.Cookie.HttpOnly = true and options.Cookie.SameSite = SameSiteMode.Lax on the authentication and session cookie options. In Express with express-session, use cookie: { secure: true, httpOnly: true, sameSite: 'lax' } plus app.set('trust proxy', 1) behind a TLS-terminating proxy. In PHP, set session.cookie_secure, session.cookie_httponly and session.cookie_samesite in php.ini.
7. For cookies set by software you cannot change, nginx can append attributes with proxy_cookie_flags ~ secure httponly samesite=lax; (nginx 1.19.3 and later), and Apache can rewrite them with Header edit Set-Cookie.
8. Consider the __Host- prefix for the session cookie. Browsers accept a cookie named __Host-sid only when it has Secure, Path=/ and no Domain attribute, so the protection cannot be removed by accident.
9. Verify again in the browser's cookie table after logging in, since that is where the cookies that matter most are set.
Check your own site
Start with the free passive scan: no account needed, and you get an A to F grade covering headers, TLS, cookies, DNS and other configuration a visitor can observe. It does not send attack payloads, so it will not confirm every class of flaw. The full scan, available with a free account, runs the active checks, including the one described on this page.