When you set a session cookie, the flags matter as much as the value. Without httpOnly, page scripts can read it. Without secure, it can travel over plain HTTP where it is easy to intercept. Without sameSite, the browser attaches it to cross-site requests, which enables CSRF. The fix is to set all three, and it is a one-line change.
Why it's a problem
Each missing flag removes a specific protection. No httpOnly means an XSS bug can steal the session. No secure means the cookie can leak on any non-HTTPS request or downgrade. No sameSite means another site can make the browser send the cookie on a forged request. Because the cookie authenticates the user, weakening its flags weakens every route that trusts the session.
The pattern
// flags omitted: readable by JS, sent over HTTP, attached cross-site
res.cookie("session", sessionId);The fix
res.cookie("session", sessionId, {
httpOnly: true, // scripts cannot read it
secure: true, // HTTPS only
sameSite: "lax", // not sent on cross-site requests by default
maxAge: 60 * 60 * 1000,
path: "/",
});Why AI tools write this
The minimal cookie call works in development, where the app runs over http://localhost and nothing cross-site is happening, so the missing flags never cause a visible problem. The assistant optimizes for code that runs now, and the insecure defaults only bite in production. It also frequently omits secure specifically because it breaks local HTTP testing.
The quick fix
- Set httpOnly: true, secure: true, and sameSite: 'lax' (or 'strict') on every session cookie.
- Gate secure on an environment flag if you need local HTTP, but keep it on in production.
- Set a sensible maxAge or expiry so sessions do not live forever.
- Use 'strict' sameSite for cookies that should never be sent cross-site.
Common questions
What does sameSite: 'lax' vs 'strict' actually do?
lax blocks the cookie on cross-site POST, PUT, and DELETE but allows it on top-level navigations like clicking a link. strict blocks it on all cross-site requests, including navigating from another site, which can log users out when following links.
If I set httpOnly on the session cookie, can my JavaScript still call authenticated endpoints?
Yes. The cookie is sent automatically by the browser with every request to your domain using credentials: 'include'. Your JavaScript does not need to read the token; the browser handles it.
What is a sensible maxAge for a session cookie?
Common practice is 24 hours to 30 days depending on the security requirements. For high-security flows, use a shorter window. Setting no maxAge creates a session cookie that expires when the browser closes.