Guide

Cookie Security: Secure, HttpOnly and SameSite Explained

Three cookie attributes protect a login session: Secure (never sent in the clear), HttpOnly (hidden from JavaScript so XSS can't steal it), and SameSite (not sent cross-site, which blocks CSRF). A correct session cookie sets all three; a missing SameSite is only a minor gap because browsers default to Lax.

By Paul Rudenko, Security ResearcherUpdated Jun 30, 20269 min read

A cookie is just a small string your server asks the browser to store and send back on every request. When that cookie holds a login session, it is the user's identity — so how the cookie is configured decides how easily an attacker can steal that identity. Three attributes do almost all of the work: Secure, HttpOnly and SameSite. This guide explains what each one does, the prefixes and scoping rules that harden them further, and how to set a session cookie correctly.

Each flag has its own focused guide if you want to go straight to one:

FlagWhat it does
The Secure flagSends the cookie only over HTTPS, so a session token is never exposed in the clear.
The HttpOnly flagHides the cookie from JavaScript, so a cross-site scripting bug can't steal the session.
The SameSite attributeControls cross-site sending to defend against CSRF; a missing value is only a minor gap.

Why session cookies are a security boundary

HTTP is stateless: the server forgets you between requests. A session cookie bridges that gap — after you log in, the server hands the browser a token and trusts whoever presents that token on the next request. That convenience is also the risk. Anyone who obtains the cookie can act as you without ever knowing your password, and most account-takeover attacks target the cookie rather than the credentials.

There are three realistic ways a session cookie leaks: it travels over plain HTTP and is read on the network, JavaScript injected through a cross-site scripting (XSS) bug reads it, or a malicious site rides your logged-in session through a cross-site request (CSRF). Each of the three flags closes one of those doors.

The three flags that matter

Secure — never send the cookie in the clear

The Secure flag tells the browser to send the cookie only over HTTPS. Without it, a single plain-HTTP request — a typed URL, an old bookmark, a downgrade attack — puts the session token on the wire where anyone on the network path can read it. On an HTTPS site every cookie should be Secure; there is no downside. Full guide to the Secure flag →

HttpOnly — keep the cookie away from JavaScript

HttpOnly hides the cookie from document.cookie, so page JavaScript cannot read it. This is what turns a cross-site scripting bug from "attacker runs script" into "attacker runs script but still can't steal the session." A login or session cookie should always be HttpOnly. The only cookies that should omit it are ones the front-end genuinely must read, such as a CSRF double-submit token. Full guide to HttpOnly →

SameSite — control cross-site sending

SameSite decides whether the cookie is attached to requests that originate from other sites. Strict never sends it cross-site; Lax sends it only on top-level navigations; None always sends it (and then Secure is mandatory). This is the primary defence against CSRF. Crucially, modern browsers now default to Lax when the attribute is absent, so a missing SameSite is a minor hardening gap rather than an open vulnerability. Full guide to SameSite →

Severity should track real risk. A login cookie without Secure or HttpOnly is genuinely exploitable and deserves urgency. A missing SameSite is informational because the browser default already covers the common case. Tools that flag all three as the same "medium" are crying wolf.

Cookie name prefixes: __Host- and __Secure-

Two special name prefixes let the browser enforce extra guarantees, so a misconfiguration can't silently weaken the cookie:

  • __Secure- — the browser will only accept the cookie if it carries the Secure flag and was set over HTTPS.
  • __Host- — the strongest option: the cookie must be Secure, set with Path=/, and have no Domain attribute, which locks it to the exact host that set it (no subdomains). Ideal for session cookies.

If a cookie claims one of these prefixes but breaks the rules, the browser rejects it — so a prefix violation is a real bug, not a style nit.

Don't scope session cookies too broadly

The Domain attribute controls which hosts receive the cookie. Setting Domain=example.com on a session cookie shares it with every subdomain —blog.example.com, status.example.com, a marketing microsite — so a vulnerability on any one of them can expose the session for all of them. Unless you genuinely need cross-subdomain sessions, omit Domain (or use the __Host- prefix) so the cookie stays bound to the single host that issued it.

The safe baseline for a session/auth cookie on an HTTPS site is all three flags together:

Set-Cookie: __Host-sid=<token>; Secure; HttpOnly; SameSite=Lax; Path=/

That cookie is sent only over HTTPS, is invisible to JavaScript, is not attached to cross-site requests, and — thanks to the __Host- prefix — is locked to the exact host with no Domain widening. Use SameSite=Strict if your app never relies on following an inbound link into an authenticated page; use None; Secure only for cookies that must work in a genuine cross-site context (for example an embedded widget).

Express, Django and Rails all expose these as a one-line cookie option:

// Express (cookie-session / express-session)
res.cookie("sid", token, { secure: true, httpOnly: true, sameSite: "lax", path: "/" });

# Django settings.py
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"

# Rails config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store,
  key: "_app_session", secure: true, httponly: true, same_site: :lax

Want to see how your site's cookies are configured right now?

Run the free cookie checker

What a passive check can and can't see

A public, unauthenticated checker reads the Set-Cookie headers your site returns to an anonymous visitor — the homepage plus any redirect hops. That covers the cookies most sites set before login, but cookies issued after you sign in are not visible to a passive check. Treat a clean public result as "the anonymous surface is healthy" and review your post-login cookies in the same way during development.

For the individual flags, read the focused guides: Secure, HttpOnly and SameSite. To see your cookies graded in seconds, use the cookie security checker.

Frequently asked questions

What are the most important cookie security flags?

Three: Secure, HttpOnly and SameSite. Secure stops the cookie being sent over plain HTTP, where it could be read on the network. HttpOnly hides the cookie from JavaScript, so a cross-site scripting bug can't read a logged-in user's session token. SameSite controls whether the cookie is attached to cross-site requests, which is the core defence against cross-site request forgery (CSRF). For a session or login cookie on an HTTPS site, set all three together, as in Set-Cookie: __Host-sid=<token>; Secure; HttpOnly; SameSite=Lax; Path=/. Weigh them by real risk, though: a session cookie missing Secure or HttpOnly is genuinely exploitable, whereas a missing SameSite is minor because browsers already default to Lax. The __Host- name prefix adds browser-enforced guarantees on top, and it's fine — expected, even — to leave HttpOnly off a cookie the front-end must read, such as a CSRF double-submit token.

What is the difference between Secure and HttpOnly?

They defend against different attacks and you usually want both on a session cookie. Secure controls the transport: the browser will only send a Secure cookie over an encrypted HTTPS connection, so it can't be sniffed on the network or leaked by an accidental plain-HTTP request. HttpOnly controls access from code on the page: a cookie marked HttpOnly is not exposed to JavaScript through document.cookie, so even if an attacker injects script via an XSS vulnerability, they can't read the session token. Concretely, Set-Cookie: sid=<token>; Secure; HttpOnly sets both at once. Secure protects the cookie in transit; HttpOnly protects it from in-page scripts. The two are independent — a cookie can have one without the other — which is why a scanner checks for each separately, and why a login cookie missing either one is worth fixing promptly.

Is a missing SameSite attribute a vulnerability?

Not on its own, and not the way it once was. Since 2020 all major browsers treat a cookie with no SameSite attribute as SameSite=Lax by default, which already blocks the most common CSRF vector — a cross-site form POST or background request. So a missing SameSite is a hardening gap worth closing — set it explicitly to Lax or Strict for clarity, since defaults can vary at the edges — but it is informational rather than an active vulnerability. By contrast, a session cookie missing Secure or HttpOnly is genuinely exploitable and should be fixed promptly. The one real SameSite error is SameSite=None without Secure: browsers reject that cookie outright, so it is silently dropped and whatever relied on it quietly breaks. That is why an honest checker rates a bare missing SameSite far below a missing Secure or HttpOnly flag rather than lumping all three together.

What does the __Host- cookie prefix do?

The __Host- prefix asks the browser to enforce the strongest cookie guarantees: it will only accept a __Host- cookie if it is marked Secure, set with Path=/, and has no Domain attribute — which locks the cookie to the exact host that set it, with no subdomains. A valid example is Set-Cookie: __Host-sid=<token>; Secure; HttpOnly; SameSite=Lax; Path=/. It is the recommended prefix for session cookies because a misconfiguration can't silently weaken the cookie; the browser simply rejects a __Host- cookie that breaks the rules, so a prefix violation is a real bug rather than a style nit. The related __Secure- prefix is weaker — it only requires the Secure flag and still allows a Domain attribute — so reach for __Host- whenever the cookie doesn't genuinely need to be shared across subdomains.

Related guides

See your whole external attack surface

One page is a start. The full external scan covers TLS, headers, DNS, exposed files, open services and known-exploited CVEs across your whole domain.

See the full scan →