HttpOnly, Secure, SameSite — the Three That Matter
Cookie Auth Details
Every cookie-based auth scheme depends on these three flags. Get them right.
What you'll learn
- Set HttpOnly correctly
- Use Secure in production
- Pick a SameSite value
The previous lesson set cookie flags. This lesson explains why each one matters, and what breaks when you get it wrong.
HttpOnly: true
JavaScript on the page cannot read an HttpOnly cookie:
document.cookie // doesn't include HttpOnly cookies Why it matters: if your site has an XSS bug, the attacker’s JS
cannot exfiltrate the session cookie. Without HttpOnly, one
small XSS = total account takeover.
Always on for session/auth cookies.
Secure: true
The cookie is only sent over HTTPS. Over plain HTTP, the browser doesn’t include it.
In production: always on. Without it, a network attacker can sniff session cookies on the wire.
In local dev (HTTP): set it to false, or the cookie won’t stick
to your http://localhost server.
SameSite
When does the browser send your cookie to your domain from elsewhere?
| Value | When sent |
|---|---|
Strict | Only on top-level requests from your domain |
Lax | Same-site requests + top-level GETs cross-site (e.g. clicking a link to you) |
None | Always — but requires Secure: true |
Lax is the modern default. It prevents most CSRF (no cross-site
POSTs send your cookie) while still letting users follow links to
you and stay logged in.
Strict is more secure but breaks “click an email link → you’re
logged in” flows. Use it for very sensitive cookies, fine to use
Lax for everything else.
None is needed for cross-origin SPAs that hit your API. Always
pair with Secure.
CSRF Protection
With SameSite=Lax, most CSRF attacks already fail. For complete
protection, add CSRF tokens for state-changing requests (covered in
the CSRF lesson).
Cookie Lifetime
cookie: {
maxAge: 24 * 60 * 60 * 1000, // 24 hours in ms
} Session cookies (no maxAge) die when the browser closes.
Persistent cookies survive — handy for “remember me.”
Tip: rotate the session ID periodically. express-session has
req.session.regenerate(cb) — call it after sensitive operations
(login, privilege change).
Clearing
res.clearCookie(name) removes a cookie. To clear a cookie set
with options, pass matching options:
res.clearCookie("session", { path: "/", domain: ".example.com" }); If the options don’t match, browsers may not clear it.
A Cookie-Hardened Default
For new projects, this triple is the right default:
{
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
} Memorize it. Apply it everywhere you set cookies.
JWT →