Secure Cookies & the __Host- Prefix

httpOnly, secure, SameSite and the __Host- prefix turn a cookie from a liability into a hardened credential store.

If you store any session id or token in a cookie, its flags are your security posture. Learn the four that matter and one prefix that quietly enforces them.

FlagWhat it does
httpOnlyJavaScript cannot read it — blocks XSS token theft.
secureSent only over HTTPS — no plaintext leak.
sameSite'lax'/'strict' withholds it cross-site — CSRF defense.
signedTamper-evident via cookie-parser secret.

The __Host- prefix

Name a cookie with the __Host- prefix and browsers refuse to accept it unless it is secure, has path=/, and has no domain. It cryptographically pins the cookie to your exact origin, defeating subdomain-injection and cookie-fixation attacks — a free, enforced-by-the-browser guarantee.

res.cookie('__Host-sid', id, {
  httpOnly: true, secure: true, sameSite: 'strict', path: '/',
});

Example

Example · javascript
import express from 'express';
import session from 'express-session';
import { RedisStore } from 'connect-redis';

const app = express();
app.set('trust proxy', 1); // required so `secure` cookies work behind a proxy

app.use(
  session({
    store: new RedisStore({ client: redisClient }), // never the default MemoryStore in prod
    name: '__Host-sid',                             // browser-enforced hardening
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    rolling: true,                                  // refresh maxAge on activity
    cookie: {
      httpOnly: true,
      secure: true,
      sameSite: 'strict',
      path: '/',                                    // required by __Host-
      maxAge: 1000 * 60 * 30,                       // 30 min, matches session TTL
    },
  }),
);

app.post('/login', (req, res) => {
  // Regenerate the session id on privilege change to prevent fixation.
  req.session.regenerate((err) => {
    if (err) return res.status(500).end();
    req.session.userId = 42;
    res.json({ ok: true });
  });
});

When to use it

  • A production login sets a Secure, HttpOnly, SameSite=Strict cookie so it is never exposed to JavaScript and only sent over HTTPS.
  • A developer uses cookie-parser with a signing secret so the app can detect if a session cookie has been tampered with in transit.
  • A multi-domain app uses SameSite=None with Secure to allow the session cookie to be sent in cross-site requests to a shared API subdomain.

More examples

Secure session cookie options

Configures the session cookie to be httpOnly, secure in production, SameSite=Strict, and expire after one day.

Example · js
const session = require('express-session');

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'strict',
    maxAge: 24 * 60 * 60 * 1000, // 1 day
  },
}));

Signed cookie with cookie-parser

Uses cookie-parser to sign the cookie value so req.signedCookies returns false if the cookie has been modified by the client.

Example · js
const cookieParser = require('cookie-parser');
app.use(cookieParser(process.env.COOKIE_SECRET));

app.get('/set', (req, res) => {
  res.cookie('userId', '42', { signed: true, httpOnly: true });
  res.json({ ok: true });
});

app.get('/get', (req, res) => {
  const userId = req.signedCookies.userId; // false if tampered
  if (!userId) return res.status(401).json({ error: 'Tampered cookie' });
  res.json({ userId });
});

Cross-site cookie for embedded API

Sets SameSite=None with Secure=true to allow the session cookie to be sent in cross-site requests, required for iFramed apps or third-party API widgets.

Example · js
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true,        // required for SameSite=None
    sameSite: 'none',    // allow cross-site — only if really needed
    maxAge: 3600000,
  },
}));

Discussion

  • Be the first to comment on this lesson.