Back to Articles
Web Security
Updated Oct 25, 2025

Complete Cookie Security Guide: HttpOnly, Secure, SameSite & Advanced Protection

If you've ever had a user's session hijacked or experienced CSRF attacks, you know how critical cookie security is. A single misconfigured cookie can compromise your entire application, but getting cookie security right doesn't have to be complicated.

This guide will show you exactly how to configure cookies securely, so you can protect your users without breaking your application's functionality.

Understanding Cookie Security Threats

Before diving into solutions, it's crucial to understand the threats cookies face:

Session Hijacking

Attackers can steal session cookies through:

  • XSS (Cross-Site Scripting): Malicious scripts accessing cookies via document.cookie
  • Network Interception: Unencrypted cookies transmitted over HTTP
  • Man-in-the-Middle Attacks: Intercepting cookies in transit

Cross-Site Request Forgery (CSRF)

Cookies sent automatically with requests can be exploited to perform unauthorized actions on behalf of authenticated users.

Cookie Theft via JavaScript

Without proper protection, cookies are accessible to JavaScript, making them vulnerable to XSS attacks.

Essential Cookie Security Attributes

HttpOnly: The First Line of Defense

The HttpOnly attribute prevents client-side JavaScript from accessing cookies, protecting against XSS attacks.

What it does:

  • Hides cookies from document.cookie API
  • Prevents XSS-based cookie theft
  • Only allows server-side access

Implementation:

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

Framework Examples:

// Express.js
res.cookie('sessionId', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax'
});

// Next.js
cookies().set('sessionId', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax'
});

Secure: Encryption in Transit

The Secure attribute ensures cookies are only sent over HTTPS connections.

What it does:

  • Prevents cookie transmission over unencrypted HTTP
  • Protects against network interception
  • Required for SameSite=None in modern browsers

Critical Note: Always use Secure in production environments.

SameSite: CSRF Protection

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

SameSite Values:

SameSite=Strict

  • Most Secure: Cookies never sent with cross-site requests
  • Use Case: High-security applications, banking, healthcare
  • Trade-off: May break legitimate cross-site flows
Set-Cookie: authToken=xyz; HttpOnly; Secure; SameSite=Strict

SameSite=Lax (Recommended Default)

  • Balanced Security: Cookies sent with top-level navigation
  • Use Case: Most web applications
  • Protection: Blocks CSRF from forms and AJAX requests
Set-Cookie: sessionId=abc; HttpOnly; Secure; SameSite=Lax

SameSite=None

  • Cross-Site Required: Cookies sent with all cross-site requests
  • Use Case: Third-party integrations, embedded widgets
  • Requirement: Must be used with Secure attribute
Set-Cookie: trackingId=123; HttpOnly; Secure; SameSite=None

Advanced Cookie Security Techniques

Cookie Prefixes: Enhanced Security

Modern browsers support cookie prefixes that enforce security attributes:

__Secure- Prefix

  • Automatically enforces Secure attribute
  • Prevents accidental HTTP transmission
Set-Cookie: __Secure-sessionId=abc123; HttpOnly; SameSite=Lax

__Host- Prefix

  • Restricts cookie to exact hostname
  • Prevents subdomain attacks
  • Automatically enforces Secure and Path=/
Set-Cookie: __Host-authToken=xyz789; HttpOnly; SameSite=Strict

Partitioned Cookies (CHIPS)

For third-party contexts, use partitioned cookies to prevent cross-site tracking:

Set-Cookie: trackingId=123; HttpOnly; Secure; SameSite=None; Partitioned

Cookie Expiration and Rotation

Short-Lived Tokens

// Short-lived access token (15 minutes)
res.cookie('accessToken', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  maxAge: 15 * 60 * 1000 // 15 minutes
});

// Longer-lived refresh token (7 days)
res.cookie('refreshToken', refreshToken, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
});

Token Rotation Strategy

// Rotate tokens on each request
app.post('/api/refresh', (req, res) => {
  const refreshToken = req.cookies.refreshToken;
  
  if (isValidRefreshToken(refreshToken)) {
    const newAccessToken = generateAccessToken();
    const newRefreshToken = generateRefreshToken();
    
    // Set new tokens
    res.cookie('accessToken', newAccessToken, {
      httpOnly: true,
      secure: true,
      sameSite: 'strict',
      maxAge: 15 * 60 * 1000
    });
    
    res.cookie('refreshToken', newRefreshToken, {
      httpOnly: true,
      secure: true,
      sameSite: 'strict',
      maxAge: 7 * 24 * 60 * 60 * 1000
    });
  }
});

Framework-Specific Implementations

Node.js/Express

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

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,        // HTTPS only
    httpOnly: true,      // No JavaScript access
    maxAge: 24 * 60 * 60 * 1000, // 24 hours
    sameSite: 'lax'      // CSRF protection
  }
}));

Next.js (App Router)

// app/api/auth/login/route.ts
import { cookies } from 'next/headers';

export async function POST(request: Request) {
  const { email, password } = await request.json();
  
  // Authenticate user
  const user = await authenticateUser(email, password);
  
  if (user) {
    const token = generateJWT(user);
    
    cookies().set('authToken', token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      maxAge: 60 * 60 * 24, // 24 hours
      path: '/'
    });
    
    return Response.json({ success: true });
  }
  
  return Response.json({ error: 'Invalid credentials' }, { status: 401 });
}

ASP.NET Core

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
  .AddCookie(options => {
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    options.Cookie.SameSite = SameSiteMode.Lax;
    options.ExpireTimeSpan = TimeSpan.FromHours(24);
    options.SlidingExpiration = true;
  });

Django

# settings.py
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_AGE = 86400  # 24 hours
SESSION_EXPIRE_AT_BROWSER_CLOSE = False

# CSRF protection
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SAMESITE = 'Lax'

Security Best Practices

1. Cookie Classification and Naming

Session Cookies:

Set-Cookie: __Host-sessionId=abc123; HttpOnly; Secure; SameSite=Strict; Path=/

Authentication Cookies:

Set-Cookie: __Secure-authToken=xyz789; HttpOnly; Secure; SameSite=Strict; Path=/

Preference Cookies:

Set-Cookie: userPreferences=theme:dark; Secure; SameSite=Lax; Max-Age=31536000

2. Environment-Specific Configuration

const cookieConfig = {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  sameSite: process.env.NODE_ENV === 'production' ? 'lax' : 'lax',
  maxAge: 24 * 60 * 60 * 1000, // 24 hours
  path: '/'
};

3. Cookie Validation and Sanitization

// Validate cookie values
function validateCookieValue(value) {
  // Remove dangerous characters
  return value.replace(/[<>\"'&]/g, '');
}

// Set validated cookie
res.cookie('userData', validateCookieValue(userData), {
  httpOnly: true,
  secure: true,
  sameSite: 'lax'
});

4. Secure Cookie Deletion

// Properly delete cookies
res.clearCookie('sessionId', {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  path: '/'
});

Testing Cookie Security

Manual Testing Checklist

  1. HttpOnly Verification:

    // Should return empty string
    console.log(document.cookie);
    
  2. Secure Flag Testing:

    • Test over HTTP (should not send cookie)
    • Test over HTTPS (should send cookie)
  3. SameSite Testing:

    <!-- Test CSRF protection -->
    <form action="https://target-site.com/transfer" method="POST">
      <input type="hidden" name="amount" value="1000">
      <input type="submit" value="Click me">
    </form>
    

Automated Testing

// Jest test for cookie security
describe('Cookie Security', () => {
  test('should set secure cookie attributes', async () => {
    const response = await request(app)
      .post('/login')
      .send({ email: '[email protected]', password: 'password' });
    
    const cookies = response.headers['set-cookie'];
    const sessionCookie = cookies.find(c => c.startsWith('sessionId'));
    
    expect(sessionCookie).toContain('HttpOnly');
    expect(sessionCookie).toContain('Secure');
    expect(sessionCookie).toContain('SameSite=Lax');
  });
});

Common Cookie Security Mistakes

1. Missing HttpOnly

<!-- VULNERABLE -->
Set-Cookie: sessionId=abc123; Secure; SameSite=Lax

<!-- SECURE -->
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax

2. Using SameSite=None Without Secure

<!-- VULNERABLE -->
Set-Cookie: trackingId=123; SameSite=None

<!-- SECURE -->
Set-Cookie: trackingId=123; Secure; SameSite=None

3. Storing Sensitive Data in Cookies

// VULNERABLE - Don't store sensitive data
res.cookie('userData', JSON.stringify({
  password: 'hashedPassword',
  creditCard: '1234-5678-9012-3456'
}));

// SECURE - Store only session identifiers
res.cookie('sessionId', generateSecureSessionId());

4. Inconsistent Cookie Configuration

// VULNERABLE - Inconsistent security settings
app.use(session({
  cookie: { secure: false } // Different from other cookies
}));

// SECURE - Consistent configuration
const secureCookieConfig = {
  httpOnly: true,
  secure: true,
  sameSite: 'lax'
};

Monitoring and Alerting

Cookie Security Monitoring

// Monitor for insecure cookie usage
app.use((req, res, next) => {
  const cookies = req.headers.cookie;
  
  if (cookies && !req.secure) {
    // Log potential security issue
    console.warn('Insecure cookies detected over HTTP');
  }
  
  next();
});

Security Headers Validation

// Validate cookie security headers
function validateCookieSecurity(req, res, next) {
  const cookies = req.headers.cookie;
  
  if (cookies) {
    // Check for missing security attributes
    const insecureCookies = cookies.split(';').filter(cookie => 
      !cookie.includes('HttpOnly') || 
      !cookie.includes('Secure') || 
      !cookie.includes('SameSite')
    );
    
    if (insecureCookies.length > 0) {
      console.warn('Insecure cookies detected:', insecureCookies);
    }
  }
  
  next();
}

Compliance Considerations

GDPR Compliance

  • Implement cookie consent mechanisms
  • Provide clear cookie policies
  • Allow users to opt-out of non-essential cookies

PCI DSS Compliance

  • Use strong encryption for payment-related cookies
  • Implement proper access controls
  • Regular security assessments

HIPAA Compliance

  • Encrypt all cookies containing PHI
  • Implement audit logging for cookie access
  • Use secure transmission protocols

Keep your applications secure with continuous monitoring using Barrion's security dashboard to detect cookie security issues and other vulnerabilities in real-time.

Framework examples

Node/Express:

res.cookie('sessionId', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  path: '/',
})

Next.js (Route Handlers):

import { cookies } from 'next/headers'

cookies().set('sessionId', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  path: '/',
})

ASP.NET Core:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
  .AddCookie(options => {
    options.Cookie.HttpOnly = true;
    options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
    options.Cookie.SameSite = SameSiteMode.Lax;
  });

Django:

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

Testing

  • Browser DevTools → Application → Cookies: verify attributes
  • Check Set-Cookie headers in responses
  • Exercise login, logout, and cross‑site flows

Browser nuances

  • Safari handles some cross‑site navigation and redirect chains differently. Test login/return flows with SameSite=Lax, redirects, and multiple tabs/windows.
  • Mobile webviews can cache aggressively. Ensure logouts clear relevant cookies.

Common mistakes

  • Missing Secure in production
  • Using SameSite=None without Secure
  • Storing JWTs in localStorage instead of HttpOnly cookies

Advanced tips

  • Keep cookie lifetime short and rotate sessions on privilege changes
  • Bind sessions to device hints (IP ranges, User‑Agent) if risk warrants
  • Pair with CSRF tokens on state‑changing POST requests

Conclusion

Set cookies with HttpOnly, Secure, and a sensible SameSite by default. Test user flows and third‑party integrations, especially authentication, so that the security changes doesn’t break functionality.

Keep tabs on regressions with ongoing security checks in the Barrion dashboard.

Frequently asked questions

Q: What SameSite value should I use?

A: "Lax" is a safe default for most sessions. Use "Strict" for sensitive flows. Use "None" only when third-party contexts are required, and always with "Secure".

Q: Where should I store tokens?

A: Prefer HttpOnly cookies for session tokens to mitigate XSS. Avoid localStorage for long-lived tokens.

Trusted by IT Professionals

IT professionals worldwide trust Barrion for comprehensive vulnerability detection.
Get detailed security reports with actionable fixes in under 60 seconds.

Barrion logo iconBarrion

Barrion delivers automated security scans and real-time monitoring to keep your applications secure.

Contact Us

Have questions or need assistance? Reach out to our team for support.

© 2025 Barrion - All Rights Reserved.