Skip to main content

Command Palette

Search for a command to run...

Cookie Security: HttpOnly Secure SameSite

Learn: Cookie Security: HttpOnly Secure SameSite

Updated
6 min readView as Markdown
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

Cookie Security: HttpOnly, Secure, SameSite

1. HttpOnly Flag

Problem

JavaScript can access cookies via document.cookie, making them vulnerable to XSS attacks. Malicious scripts can steal session tokens and authentication credentials.

Solution

Set the HttpOnly flag to prevent JavaScript from accessing cookies. Only the server can read these cookies via HTTP headers.

Code

Node.js/Express:

app.use(session({
  secret: 'your-secret-key',
  resave: false,
  saveUninitialized: true,
  cookie: {
    httpOnly: true,      // Prevents JS access
    maxAge: 3600000      // 1 hour
  }
}));

Python/Flask:

@app.route('/login', methods=['POST'])
def login():
    response = make_response(redirect('/dashboard'))
    response.set_cookie(
        'session_token',
        value=generate_token(),
        httponly=True,      # Prevents JS access
        max_age=3600
    )
    return response

PHP:

setcookie(
    'session_id',
    $sessionId,
    [
        'httponly' => true,  // Prevents JS access
        'expires' => time() + 3600,
        'path' => '/',
        'samesite' => 'Strict'
    ]
);

Tips

  • ✅ Always use HttpOnly for authentication cookies
  • ✅ Combine with other flags for defense-in-depth
  • ✅ Monitor for XSS vulnerabilities in your application
  • ⚠️ Non-sensitive cookies (tracking, preferences) can remain accessible to JS

2. Secure Flag

Problem

Cookies transmitted over unencrypted HTTP connections can be intercepted by man-in-the-middle (MITM) attacks. Attackers can capture session tokens in transit.

Solution

Set the Secure flag to ensure cookies are only transmitted over HTTPS connections, encrypting data in transit.

Code

Node.js/Express:

app.use(session({
  secret: 'your-secret-key',
  cookie: {
    secure: true,        // HTTPS only
    httpOnly: true,
    sameSite: 'Strict',
    maxAge: 3600000
  }
}));

// Enforce HTTPS in production
if (process.env.NODE_ENV === 'production') {
  app.use((req, res, next) => {
    if (req.header('x-forwarded-proto') !== 'https') {
      res.redirect(`https://${req.header('host')}${req.url}`);
    } else {
      next();
    }
  });
}

Python/Flask:

app.config['SESSION_COOKIE_SECURE'] = True  # HTTPS only
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Strict'

@app.before_request
def enforce_https():
    if not request.is_secure and app.env == 'production':
        url = request.url.replace('http://', 'https://', 1)
        return redirect(url, code=301)

Nginx Configuration:

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    # Redirect HTTP to HTTPS
    error_page 497 =301 https://$host$request_uri;

    location / {
        proxy_pass http://backend;
        proxy_cookie_flags ~ secure httponly samesite=strict;
    }
}

Tips

  • ✅ Always use HTTPS in production
  • ✅ Use HSTS headers to enforce HTTPS
  • ✅ Obtain valid SSL/TLS certificates (Let's Encrypt is free)
  • ✅ Test with secure: false only in development
  • ⚠️ Localhost development can bypass Secure flag requirement

3. SameSite Attribute

Problem

Cross-Site Request Forgery (CSRF) attacks trick users into making unwanted requests to other sites. Cookies are automatically sent with cross-origin requests, allowing attackers to perform actions on behalf of users.

Solution

Set SameSite to control when cookies are sent with cross-origin requests. Three levels: Strict, Lax, and None.

Code

Node.js/Express:

// Strict: Only same-site requests
app.use(session({
  cookie: {
    sameSite: 'Strict',  // Most restrictive
    secure: true,
    httpOnly: true
  }
}));

// Lax: Same-site + top-level navigation (default in modern browsers)
app.use(session({
  cookie: {
    sameSite: 'Lax',     // Balanced approach
    secure: true,
    httpOnly: true
  }
}));

// None: All requests (requires Secure flag)
app.use(session({
  cookie: {
    sameSite: 'None',    // Cross-site allowed
    secure: true,        // REQUIRED with None
    httpOnly: true
  }
}));

Python/Flask:

# Strict
app.config['SESSION_COOKIE_SAMESITE'] = 'Strict'

# Lax (recommended for most apps)
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'

# None (for cross-origin scenarios)
app.config['SESSION_COOKIE_SAMESITE'] = 'None'
app.config['SESSION_COOKIE_SECURE'] = True  # Required

PHP:

// Strict
setcookie('session_id', $value, [
    'samesite' => 'Strict',
    'secure' => true,
    'httponly' => true
]);

// Lax
setcookie('session_id', $value, [
    'samesite' => 'Lax',
    'secure' => true,
    'httponly' => true
]);

// None
setcookie('session_id', $value, [
    'samesite' => 'None',
    'secure' => true,  // REQUIRED
    'httponly' => true
]);

SameSite Behavior Table:

ModeSame-Site RequestsCross-Site GETCross-Site POST
Strict✅ Sent❌ Not sent❌ Not sent
Lax✅ Sent✅ Sent❌ Not sent
None✅ Sent✅ Sent✅ Sent

Tips

  • ✅ Use Strict for sensitive operations (banking, admin panels)
  • ✅ Use Lax for general applications (default in modern browsers)
  • ✅ Use None only when cross-origin cookies are necessary
  • None requires Secure flag (HTTPS only)
  • ⚠️ Older browsers don't support SameSite; use CSRF tokens as fallback

Code

Node.js/Express (Production-Ready):

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

const app = express();

// Security headers
app.use(helmet());

// CSRF protection
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: false });

// Session configuration
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,           // Prevent XSS access
    secure: process.env.NODE_ENV === 'production',  // HTTPS only
    sameSite: 'Strict',       // CSRF protection
    maxAge: 1000 * 60 * 60,   // 1 hour
    domain: process.env.COOKIE_DOMAIN
  }
}));

// Login route
app.post('/login', csrfProtection, (req, res) => {
  // Authenticate user
  req.session.userId = user.id;
  req.session.regenerate((err) => {
    if (err) return res.status(500).send('Session error');
    res.redirect('/dashboard');
  });
});

// Logout route
app.post('/logout', (req, res) => {
  req.session.destroy((err) => {
    if (err) return res.status(500).send('Logout error');
    res.clearCookie('connect.sid');
    res.redirect('/');
  });
});

// HTTPS enforcement
if (process.env.NODE_ENV === 'production') {
  app.use((req, res, next) => {
    if (req.header('x-forwarded-proto') !== 'https') {
      res.redirect(`https://${req.header('host')}${req.url}`);
    } else {
      next();
    }
  });
}

app.listen(3000);

Python/Flask (Production-Ready):

from flask import Flask, session, redirect, request
from flask_session import Session
from flask_talisman import Talisman
import os

app = Flask(__name__)

# Security headers
Talisman(app, force_https=True)

# Session configuration
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
app.config['PERMANENT_SESSION_LIFETIME'] = 3600
app.config['SESSION_TYPE'] = 'filesystem'

Session(app)

@app.route('/login', methods=['POST'])
def login():
    # Authenticate user
    session['user_id'] = user.id
    session.permanent = True
    return redirect('/dashboard')

@app.route('/logout', methods=['POST'])
def logout():
    session.clear()
    return redirect('/')

if __name__ == '__main__':
    app.run(ssl_context='adhoc', debug=False)

5. Testing & Verification

Browser DevTools

// Check cookie flags in DevTools → Application → Cookies
// Look for:
// - HttpOnly: ✅ (no JS access)
// - Secure: ✅ (HTTPS only)
// - SameSite: Strict/Lax/None

// Test XSS protection
document.cookie  // Should NOT show HttpOnly cookies

cURL Testing

# Check Set-Cookie headers
curl -i https://example.com/login

# Expected output:
# Set-Cookie: session_id=abc123; Path=/; HttpOnly; Secure; SameSite=Strict

OWASP ZAP / Burp Suite

  • Scan for missing cookie flags
  • Test CSRF vulnerabilities
  • Verify HTTPS enforcement

6. Security Checklist

  • HttpOnly: Enabled for all authentication cookies
  • Secure: Enabled in production (HTTPS only)
  • SameSite: Set to Strict or Lax
  • HTTPS: Enforced site-wide
  • HSTS: Enabled with Strict-Transport-Security header
  • CSRF Tokens: Implemented as secondary protection
  • Session Regeneration: After login/privilege changes
  • Cookie Expiration: Set reasonable timeouts
  • Secure Domain: Cookies scoped to specific domain
  • Monitoring: Log suspicious cookie access attempts

7. Common Pitfalls

❌ Wrong✅ Correct
secure: false in productionsecure: true with HTTPS
Missing httpOnly flaghttpOnly: true always
sameSite: 'None' without securesameSite: 'None', secure: true
No session regenerationRegenerate after login
Storing sensitive data in cookiesStore only session ID
No CSRF protectionImplement CSRF tokens + SameSite
HTTP in productionEnforce HTTPS everywhere

Summary

HttpOnly → Prevents XSS attacks
Secure → Prevents MITM attacks
SameSite → Prevents CSRF attacks

Use all three together for comprehensive cookie security.