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.