Skip to main content

Setting cookie flags correctly

Secure, HttpOnly and SameSite — three small switches that together rule out the most common cookie-based attacks.

Why harden cookies?

Cookies — especially session cookies — are the authentication mechanism of the web. Whoever steals the cookie is logged in. Three classic attacks target cookies: XSS (a script steals the cookie via document.cookie), man-in-the-middle (the cookie is read in transit on an unencrypted connection) and CSRF (a third-party site uses the cookie for unauthorized actions). For each attack there is a matching cookie flag — and all three should be set.

Secure

With Secure the browser only sends the cookie over HTTPS. On an HTTP connection the cookie is neither set nor sent back. As a result, an attacker on the same Wi-Fi cannot intercept cookies from an unencrypted connection. Mandatory for every auth cookie.

HttpOnly

With HttpOnly the cookie becomes invisible to JavaScript — accessing it via document.cookie returns nothing. That makes an XSS vulnerability significantly less dangerous: even if foreign code is running in the user's browser, it cannot steal the session cookie. Mandatory for every auth cookie.

SameSite

SameSite controls whether the cookie is sent on cross-site requests. Three values:

  • SameSite=Strict — the cookie is never attached to cross-site requests. Maximum security, but UX problems: a user clicking from Google to your site is not logged in on the first visit.
  • SameSite=Lax (default since 2020) — the cookie is sent on top-level navigation (clicking a link), but not on iframes, images or POST requests from foreign sites. A good compromise.
  • SameSite=None — the cookie is always sent. Requires Secure as well. Only needed for third-party cookies (tracking, embedded widgets).

__Host- and __Secure- prefixes

Special cookie name prefixes enforce certain flags. Cookies starting with __Host- must have Secure, no Domain attribute and Path=/. __Secure- only enforces Secure. Both prefixes additionally protect against cookie-tossing attacks from subdomains.

Configuration examples

Flask:

app.config['SESSION_COOKIE_SECURE']    = True
app.config['SESSION_COOKIE_HTTPONLY']  = True
app.config['SESSION_COOKIE_SAMESITE']  = 'Lax'

Express (Node.js):

app.use(session({
  cookie: { secure: true, httpOnly: true, sameSite: 'lax' }
}))

PHP (session.cookie_secure etc. via php.ini or ini_set):

session.cookie_secure   = 1
session.cookie_httponly = 1
session.cookie_samesite = "Lax"

What cookie flags do not protect against

  • Logical CSRF despite SameSite=Lax — on GET requests that change state (which they shouldn't anyway). Solution: GET = read-only, otherwise use a CSRF token.
  • Session hijacking via XSS — if the attacker can run code directly in your context, they can perform actions instead of stealing cookies. CSP against XSS remains important.
  • Cookie content — flags protect transport, not content. Sensitive data does not belong in the cookie; keep it server-side in the session store.

Recommendation

For every auth cookie: Secure; HttpOnly; SameSite=Lax. For strict security (banking, admin panels): SameSite=Strict. An additional __Host- prefix costs nothing and rules out subdomain attacks.

Check it yourself: HTTP-Header

HSTS, CSP, X-Frame-Options & Co. Enter a domain and see in seconds how your COOKIE-FLAGS is doing.

Also in the glossary