Cookie Security: Flags, Prefixes, and Best Practices

How cookie attributes protect against session hijacking, CSRF, and cross-site leakage -- and how to configure them correctly.

The Secure flag

A cookie with the Secure flag is only sent over HTTPS connections. Without it, the browser will transmit the cookie over plain HTTP, allowing any network observer to intercept it.

Set-Cookie: session=abc123; Secure

This flag should be set on every cookie in production. There is no legitimate reason to send cookies over unencrypted HTTP. Even if your site redirects HTTP to HTTPS, an attacker who intercepts the initial HTTP request will see any cookies that lack the Secure flag.

Note that Secure does not encrypt the cookie value -- it only restricts the transport. The cookie is still visible in browser developer tools and to any JavaScript on the page (unless HttpOnly is also set).

The HttpOnly flag

A cookie with HttpOnly cannot be accessed by JavaScript via document.cookie, the Cookie Store API, or any other client-side API. It is only sent with HTTP requests.

Set-Cookie: session=abc123; Secure; HttpOnly

This is the primary defense against session theft via XSS. If an attacker injects a script into your page, they cannot read HttpOnly cookies and exfiltrate them to their server. The cookie is still sent with every request to your origin, but the attacker's JavaScript cannot touch it.

Set HttpOnly on all session cookies and authentication tokens. Only omit it for cookies that JavaScript genuinely needs to read, such as a CSRF token (which needs to be read by JavaScript to include in request headers) or a UI preference cookie.

SameSite attribute

The SameSite attribute controls whether a cookie is sent with cross-site requests, providing protection against CSRF (cross-site request forgery) attacks.

SameSite=Strict

The cookie is never sent with cross-site requests. If a user clicks a link to your site from an external page, the cookie is not included in that initial request. This is the most secure setting but can hurt usability -- users following links from emails or other sites will arrive unauthenticated on the first request.

Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Strict

SameSite=Lax

The cookie is sent with top-level navigations (clicking a link) but not with cross-site subrequests (images, iframes, AJAX/fetch). This provides CSRF protection for state-changing requests (POST, PUT, DELETE) while preserving usability for navigation.

Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Lax

Lax is the default in modern browsers (Chrome 80+, Firefox 69+, Edge 80+). If you omit SameSite entirely, the browser treats it as Lax. However, explicitly setting it is best practice because older browsers default to None (no restriction).

SameSite=None

The cookie is sent with all cross-site requests, including in iframes and AJAX. This is required for legitimate cross-site use cases like third-party embedded widgets, single sign-on across domains, and payment provider integrations.

Set-Cookie: widget_session=xyz; Secure; HttpOnly; SameSite=None

SameSite=None requires the Secure flag. Browsers reject SameSite=None cookies without Secure. Only use this when cross-site sending is genuinely needed.

Cookie prefixes: __Host- and __Secure-

Cookie prefixes are a defense-in-depth mechanism that prevents certain types of cookie injection and scope manipulation attacks.

__Host- prefix

A cookie whose name starts with __Host- must be:

Set-Cookie: __Host-session=abc123; Secure; HttpOnly; SameSite=Lax; Path=/

This is the strongest cookie binding available. It prevents a subdomain from overwriting the cookie (because the cookie has no Domain attribute, it is bound to the exact host). It also prevents an attacker who controls a sibling subdomain from setting a cookie that shadows yours.

__Secure- prefix

A cookie whose name starts with __Secure- must be set with the Secure flag from a secure origin. It is less restrictive than __Host- because it allows a Domain attribute.

Set-Cookie: __Secure-prefs=dark; Secure; SameSite=Lax; Domain=example.com; Path=/

Use __Secure- when you need a cookie to be shared across subdomains but still want the guarantee that it was originally set over HTTPS.

Session cookie hardening checklist

For a secure session cookie, apply all of the following:

Set-Cookie: __Host-session=<token>; Secure; HttpOnly; SameSite=Lax; Path=/; Max-Age=86400

Additional measures

Framework examples

Express.js (Node):

app.use(session({
  name: '__Host-session',
  cookie: {
    secure: true,
    httpOnly: true,
    sameSite: 'lax',
    path: '/',
    maxAge: 86400000 // 24 hours in milliseconds
  },
  // ... store configuration
}));

Nginx proxy (setting cookie flags on upstream response):

# Add flags to cookies from a backend that does not set them
proxy_cookie_flags ~ secure httponly samesite=lax;

Apache (mod_headers):

# Append Secure and HttpOnly to all Set-Cookie headers
Header always edit Set-Cookie ^(.*)$ "$1; Secure; HttpOnly; SameSite=Lax"

Check your site's HTTP headers with spectra