All articles
Web SecurityCookiesHTTP Headers

HttpOnly, Secure, SameSite: Cookie Flags That Actually Matter

Understand cookie security flags HttpOnly, Secure and SameSite with real Set-Cookie examples, framework configs, and common pitfalls to avoid.

WebSentry TeamMay 15, 20266 min read

Cookies are still the backbone of authentication on the web, which makes them one of the highest-value targets for attackers. A single missing flag on a session cookie can turn an XSS bug into a full account takeover, or a phishing link into a working CSRF exploit. The good news: three flags — HttpOnly, Secure, and SameSite — close most of these holes if you set them correctly.

This post breaks down what each flag does, what it doesn't do, and how to configure them properly across common stacks. We'll also cover the edge cases that catch teams out (cross-site iframes, OAuth redirects, subdomains) and how to verify your setup.

What each cookie security flag actually does

HttpOnly

The HttpOnly flag tells the browser: "don't expose this cookie to JavaScript." Any attempt to read it via document.cookie returns an empty string for that cookie.

  • Protects against: session theft via XSS. If an attacker injects a script, they can't exfiltrate the session token.
  • Does not protect against: CSRF, network interception, or malicious scripts performing authenticated requests on the user's behalf.

Rule of thumb: every authentication, session, or CSRF-token cookie should be HttpOnly. The only cookies that shouldn't be are ones your frontend genuinely needs to read (e.g. a non-sensitive UI preference).

Secure

The Secure flag forces the cookie to only be sent over HTTPS. Without it, a user on a hostile network can downgrade a request to HTTP and capture the cookie in plaintext.

  • Protects against: network sniffing, man-in-the-middle cookie theft, accidental leaks via mixed-content requests.
  • Does not protect against: XSS, CSRF, or compromised TLS endpoints.

If you're on HTTPS in production (and you should be), there is no reason not to set Secure. Modern browsers also require Secure if you want to use SameSite=None.

SameSite

The SameSite attribute controls whether the cookie is sent on cross-site requests. It has three values:

  • Strict — the cookie is never sent on cross-site requests, including top-level navigation. Strongest CSRF protection, but breaks flows like clicking a link from an email into an authenticated page.
  • Lax — the cookie is sent on top-level GET navigation but not on cross-site POSTs, iframes, or subresource requests. Good default for session cookies.
  • None — the cookie is sent on all cross-site requests. Required for third-party contexts (embedded widgets, cross-domain SSO). Must be paired with Secure.

Most browsers now default to Lax if you don't set SameSite explicitly, but relying on defaults is brittle — always be explicit.

What a properly secured Set-Cookie looks like

For a typical session cookie on a first-party app:

Set-Cookie: sessionid=abc123; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=3600

For a cookie used by an embedded widget on third-party domains:

Set-Cookie: widget_token=xyz; Path=/; HttpOnly; Secure; SameSite=None; Max-Age=3600

For a hardened admin session where you accept losing inbound-link UX:

Set-Cookie: admin_session=...; Path=/; HttpOnly; Secure; SameSite=Strict

Framework-specific configuration

Express (Node.js)

app.use(session({
  secret: process.env.SESSION_SECRET,
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    maxAge: 1000 * 60 * 60
  }
}));

If you're behind a proxy (Nginx, a load balancer), also set app.set('trust proxy', 1) or Express will think the connection is HTTP and refuse to send Secure cookies.

Django

In settings.py:

SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = 'Lax'

Note that Django's CSRF cookie is intentionally not HttpOnly because the JS code needs to read it to include the token in headers.

Laravel

In config/session.php:

'secure' => env('SESSION_SECURE_COOKIE', true),
'http_only' => true,
'same_site' => 'lax',

ASP.NET Core

services.Configure<CookiePolicyOptions>(options => {
    options.HttpOnly = HttpOnlyPolicy.Always;
    options.Secure = CookieSecurePolicy.Always;
    options.MinimumSameSitePolicy = SameSiteMode.Lax;
});

The mistakes that quietly break security

  1. Setting HttpOnly only on the session cookie, not the CSRF cookie. If your CSRF token cookie is readable by JS, an XSS can read it and forge requests anyway. Either use double-submit with a header (and keep the cookie HttpOnly) or accept the tradeoff knowingly.
  2. SameSite=None without Secure. Modern browsers reject this cookie entirely. Users get logged out with no obvious error.
  3. SameSite=Strict on the main session cookie. Users click a link to your site from Gmail or Slack, land logged-out, and get confused. Lax is usually the right default.
  4. Forgetting the Domain attribute on shared-subdomain setups. If app.example.com sets a cookie without Domain=.example.com, api.example.com won't see it. Conversely, setting it too broadly can leak the cookie to unrelated subdomains.
  5. Mixing flag casing inconsistently. The flags are case-insensitive, but if a custom proxy or WAF rewrites headers, inconsistency can cause bugs. Stick to one style.
  6. Leaving legacy non-flagged cookies around. Old analytics or A/B testing cookies often ship without any flags. Even if they're "non-sensitive," they're an easy red flag on a security audit.

Verifying your cookies are configured correctly

Manual check from DevTools: open Application → Cookies in Chrome or Storage → Cookies in Firefox. Every authentication-related cookie should have HttpOnly, Secure, and an explicit SameSite value.

From the command line:

curl -I https://yourapp.com/login -c - 

Look at every Set-Cookie header and confirm the flags are present.

For a continuous check across your whole site — including cookies set on deeper pages, redirects, and third-party endpoints you might have forgotten about — run a WebSentry scan. It enumerates every cookie set during a crawl and flags missing HttpOnly, Secure, or SameSite values, alongside related issues like overly broad Domain scopes and weak CSP policies that interact with cookie security.

A quick checklist before shipping

  • All session and auth cookies have HttpOnly
  • All cookies on HTTPS sites have Secure
  • Every cookie has an explicit SameSite value (don't rely on browser defaults)
  • Third-party cookies use SameSite=None; Secure
  • CSRF tokens are protected via a header + double-submit pattern, not by exposing the cookie to JS unnecessarily
  • Cookie Domain and Path are as narrow as possible
  • Sensitive cookies have reasonable Max-Age or Expires values — long-lived session tokens are an unnecessary risk

Cookie flags are one of the highest-leverage security wins available: a one-line change can neutralise entire classes of attacks. Once you've audited yours, run a free scan at websentry.dev to confirm nothing slipped through and to catch the related header and TLS issues that usually travel together.

Check your own site

Run a free security scan and see if your site has the issues covered in this article. Results in under 30 seconds.