How to Secure Cookies with HttpOnly, Secure, and SameSite
How to harden cookies with the HttpOnly, Secure and SameSite attributes, plus Path, Domain and the prefixes — and how to inspect them in browser DevTools.
A cookie is just a small piece of data a site asks the browser to store and send back on future requests — but when that cookie is a session token, it is effectively the key to a logged-in account, which makes it one of the most valuable things an attacker can steal. The good news is that a handful of cookie attributes turn an exposed cookie into a hardened one without changing what it stores or how your app uses it. This guide explains the attributes that matter — HttpOnly, Secure, SameSite, Path, Domain, and the __Host-/__Secure- prefixes — what each protects against, and how to inspect any site's cookies in seconds.
It pairs naturally with how to protect your website from common attacks and with what is a Content Security Policy and how to set one, since cookie flags and CSP together blunt the impact of cross-site scripting.
Answer first: the attributes that matter
Cookies are set by the server using the Set-Cookie response header (or by JavaScript). Each cookie can carry attributes that instruct the browser how to handle it. For security, the ones that count are:
HttpOnly— hide the cookie from JavaScript.Secure— only send it over HTTPS.SameSite— control whether it is sent on cross-site requests.PathandDomain— scope where it applies.- The
__Host-/__Secure-name prefixes — let the browser enforce a safe configuration.
A well-secured session cookie typically looks like this in the Set-Cookie header:
Set-Cookie: __Host-session=abc123; HttpOnly; Secure; SameSite=Lax; Path=/
Each part does a specific job. Let us take them in turn.
HttpOnly: keep cookies away from JavaScript
By default, any JavaScript running on a page can read that page's cookies via document.cookie. That is fine until an attacker manages to run their JavaScript on your page through cross-site scripting (XSS) — at which point they can read your users' cookies and send them off to a server they control. If the stolen cookie is a session token, the attacker can impersonate the victim.
The HttpOnly attribute closes this. A cookie marked HttpOnly is still sent automatically on requests to the server, but it is invisible to JavaScript — document.cookie cannot see it. So even if an XSS flaw lets an attacker run script, that script cannot read an HttpOnly session cookie and exfiltrate it.
To be clear about what HttpOnly is and is not: it does not prevent XSS — you still need input validation, output encoding and a Content Security Policy for that (see the CSP guide). What it does is remove one of the most damaging consequences of XSS, namely session theft. Every session and authentication cookie should be HttpOnly. The only cookies that should not be HttpOnly are the ones your client-side code genuinely needs to read, and those should never hold anything sensitive.
Secure: HTTPS only, never cleartext
The Secure attribute tells the browser to send the cookie only over an HTTPS connection and never over plain HTTP. Without it, a cookie can be transmitted on an http:// request — for example, the brief insecure request before a redirect to HTTPS, or any stray HTTP link — where a network attacker can capture it in cleartext.
Secure is the transport-layer complement to HttpOnly: where HttpOnly protects the cookie from script, Secure protects it on the wire. It works hand in hand with HSTS, which keeps the whole connection on HTTPS so there is no cleartext request in the first place — see what is HSTS and how to enable it. Belt and braces: HSTS keeps connections on HTTPS, and Secure guarantees the cookie is never sent over HTTP regardless. Every cookie of any sensitivity should be Secure.
SameSite: the CSRF defence
SameSite is the newest of the three core attributes and the one people understand least, so it is worth a careful explanation. It controls whether the browser includes the cookie on requests that originate from a different site.
Why does that matter? Because of cross-site request forgery (CSRF). In a CSRF attack, a malicious page (say, evil.example) causes the victim's browser to make a request to your site (yourbank.example) — for instance, submitting a hidden form that transfers money. Because the browser automatically attaches your cookies to requests to your domain, the forged request arrives carrying the victim's valid session cookie, and your server cannot easily tell it apart from a genuine action the user intended.
SameSite defeats this by limiting when the cookie travels on cross-site requests. It takes three values:
SameSite=Strict— the cookie is sent only on requests originating from your own site. Strongest protection, but it has a usability cost: if a user clicks a link to your site from an external page (an email, another website), the cookie is not sent on that first navigation, so they may appear logged out until they navigate within the site.SameSite=Lax— the cookie is sent on same-site requests and on top-level navigations to your site (clicking a normal link), but not on cross-site sub-requests like a background form post or an embedded image. This is the balanced choice and is the modern browser default when noSameSiteis specified, because it stops the dangerous cross-site cases while keeping inbound links working.SameSite=None— the cookie is sent on all cross-site requests, restoring the old unrestricted behaviour. This is needed for legitimate cross-site scenarios (some third-party embeds, federated logins), but it must be paired withSecure— browsers rejectSameSite=Nonewithout it.
For most session cookies, SameSite=Lax is the right default. Use Strict for the most sensitive actions where you can tolerate the inbound-link behaviour, and reserve None for genuine cross-site needs, always with Secure. Note that SameSite is a strong layer of CSRF defence but is best combined with traditional anti-CSRF tokens for critical actions, rather than relied on alone.
Path and Domain: scoping the cookie
Two more attributes control where a cookie is sent:
Pathrestricts the cookie to a URL path and its sub-paths.Path=/(the whole site) is typical; a narrower path limits the cookie to part of the site.Domaincontrols which hosts receive the cookie. If you omitDomain, the cookie is sent only to the exact host that set it (the most restrictive, usually safest). If you setDomain=example.com, the cookie is also sent to subdomains likeapp.example.com— broader, and only what you want if those subdomains genuinely need it. BroadeningDomainunnecessarily widens exposure, so default to omitting it unless you have a reason.
The principle is least scope: a cookie should be sent to the narrowest set of paths and hosts that actually need it, and no wider.
The __Host- and __Secure- prefixes
There is one more layer that is easy to add and quietly valuable: cookie name prefixes. These are not separate attributes but special prefixes on the cookie's name that cause the browser to enforce a secure configuration and reject the cookie if it does not comply.
__Secure-— a cookie whose name starts with__Secure-must be set with theSecureflag, over HTTPS. The browser refuses it otherwise.__Host-— stricter still: a cookie named with the__Host-prefix must beSecure, must have noDomainattribute (so it is locked to the exact host), and must havePath=/. The browser rejects it if any condition is unmet.
Why bother? Because they defend against cookie injection and fixation, where an attacker (often via a subdomain or a man-in-the-middle on a sibling host) tries to set or overwrite one of your cookies with a value they control. The __Host- prefix in particular ensures a cookie is bound to the exact host and cannot be shadowed by a cookie set for a parent domain or different path. For your most important cookies — session and authentication — naming them with __Host- is a cheap, strong hardening step.
A summary table
| Attribute / feature | What it does | Primary protection |
|---|---|---|
HttpOnly | Hides cookie from JavaScript | Mitigates session theft via XSS |
Secure | Sends cookie only over HTTPS | Stops capture on cleartext HTTP |
SameSite=Strict | Cookie only on same-site requests | Strong CSRF defence (stricter UX) |
SameSite=Lax | + top-level navigations to your site | Balanced CSRF defence (good default) |
SameSite=None | Cookie on all cross-site requests | Enables cross-site use (requires Secure) |
Path | Scopes cookie to a URL path | Limits where the cookie is sent |
Domain (omitted) | Locks cookie to the exact host | Avoids over-broad subdomain exposure |
__Secure- prefix | Forces Secure over HTTPS | Prevents insecure overwrite |
__Host- prefix | Forces Secure, no Domain, Path=/ | Strongest anti-injection binding |
How to inspect a site's cookies
You can check exactly how any site configures its cookies, which is useful both for auditing your own and for understanding others.
1. Browser DevTools. This is the fastest way. Open developer tools and go to the Application tab (in Chrome and Edge) or the Storage tab (in Firefox), then expand Cookies and select the site's origin. You will see a table of every cookie with columns for HttpOnly, Secure, SameSite, Path, Domain, expiry and value. A quick scan tells you whether the session cookie is HttpOnly, Secure and has a sensible SameSite — and whether anything sensitive is missing a flag.
2. The Set-Cookie response header. Cookies are assigned by the server via Set-Cookie. You can read this header directly — in DevTools under Network → (the request) → Response Headers, or from a terminal on a response that sets a cookie. This shows the attributes the server is applying at the source. For the broader skill of reading response headers, see how to read a website's HTTP headers.
3. Header and security graders. Tools and broader audits — StackOptic among them — surface cookie and header configuration as part of a security picture, so you can see cookie hygiene alongside HTTPS, CSP and the rest. The Mozilla Developer Network (MDN) documents every cookie attribute authoritatively if you want the precise specification.
Common mistakes
- Session cookies without
HttpOnly, leaving them readable by any injected script — the most common and most damaging oversight. - Missing
Secure, so a cookie can leak over a stray HTTP request. - No
SameSitepolicy (or relying solely on the default) for actions that need CSRF protection, instead of consciously choosingLaxorStrictplus tokens. SameSite=NonewithoutSecure, which browsers now reject outright — breaking the cookie.- An over-broad
Domainthat sends sensitive cookies to subdomains that do not need them. - Putting sensitive data in a non-
HttpOnlycookie your front-end reads, exposing it to script. - Skipping the
__Host-prefix on session cookies where it would add strong, free protection.
A quick cookie-hardening checklist
- Mark every session/auth cookie
HttpOnlyandSecure. - Set a deliberate
SameSitevalue —Laxas the default,Strictfor sensitive flows. - If you use
SameSite=None, always addSecure. - Omit
Domainunless subdomains genuinely need the cookie; scopePathtightly. - Prefix critical cookies with
__Host-to enforce a safe configuration. - Verify in DevTools → Application → Cookies that each flag is actually set.
- Combine with a CSP and HSTS so the layers reinforce one another.
Go deeper
- The header that keeps the connection HTTPS-only: what is HSTS and how to enable it.
- The strongest XSS backstop: what is a Content Security Policy and how to set one.
- Defend the whole site: how to protect your website from common attacks.
- Read what a server sends: how to read a website's HTTP headers.
Want your cookie, header and HTTPS configuration checked at once? Analyse any URL with StackOptic — security, performance and SEO in one free report.
Frequently asked questions
What does the HttpOnly cookie attribute do?
HttpOnly tells the browser not to expose the cookie to client-side JavaScript — it is sent on HTTP requests but cannot be read via document.cookie. Its main value is mitigating session theft through cross-site scripting (XSS): even if an attacker manages to run script on your page, it cannot read an HttpOnly session cookie and exfiltrate it. It does not stop XSS itself, but it removes one of the most damaging things stolen script can do.
What is the difference between the Secure and HttpOnly flags?
They protect against different things. Secure controls transport: a cookie marked Secure is only ever sent over an HTTPS connection, never over plain HTTP, so it cannot be captured on a cleartext request. HttpOnly controls access: a cookie marked HttpOnly cannot be read by JavaScript in the page. A sensitive cookie, such as a session token, should carry both — Secure to protect it in transit and HttpOnly to protect it from script.
What does SameSite do and which value should I use?
SameSite controls whether a cookie is sent on requests originating from other sites, which is the core of CSRF defence. SameSite=Strict sends the cookie only on same-site requests (strongest, but can log users out when following inbound links). SameSite=Lax sends it on top-level navigations to your site (a balanced, common default). SameSite=None sends it on all cross-site requests and must be paired with Secure. Choose Lax unless you have a specific reason for Strict or None.
What are the __Host- and __Secure- cookie prefixes?
They are special name prefixes that make the browser enforce a secure configuration. A cookie named with the __Secure- prefix must be set with the Secure flag over HTTPS. A cookie with the __Host- prefix must additionally have no Domain attribute and a Path of /, locking it to the exact host that set it. Their purpose is to stop a weaker or attacker-set cookie from overwriting a secure one, hardening against cookie-injection and fixation attacks.
How do I check a website's cookie security settings?
Open your browser's developer tools and go to the Application tab in Chrome or Edge (Storage in Firefox), then expand Cookies and select the site. You will see each cookie with columns for HttpOnly, Secure, SameSite, Path, Domain and expiry, so you can verify sensitive cookies are configured correctly. From a terminal, a request that returns a Set-Cookie response header shows the attributes the server is assigning.
Analyse any website with StackOptic
Get the full technology stack, performance, security and SEO report in seconds — free.
Analyse a websiteRelated articles
How to Check a Website for Malware
A practical guide to checking any website for malware: the free external scanners to use, the signs of infection, server-side checks, and what to do next.
What Is a Data Breach and How to Respond
A plain-English guide to data breaches: what counts as one, the common causes, a step-by-step incident-response plan, the GDPR 72-hour rule, and prevention.
How to Protect Your Website from Bots and Scrapers
Not all bots are bad. Tell good crawlers from abusive scrapers, spot the signals of bot traffic, and layer rate limiting, CAPTCHA, a WAF and bot management.