Session cookies are the mechanism most web applications use to maintain authentication state. Once a user logs in, the server issues a session token stored in a cookie — and every subsequent request includes that cookie, proving the user's identity. If an attacker can steal or forge that cookie, they can impersonate the user completely. Correct cookie security flags are the primary defense against this.
The Four Critical Cookie Flags
HttpOnly
The HttpOnly flag prevents JavaScript from accessing the cookie via document.cookie. This is the single most important flag for session cookies. If an attacker manages to inject JavaScript into your page (via XSS), they cannot steal the session token with document.cookie if HttpOnly is set.
Set-Cookie: sessionid=abc123; HttpOnly
The cookie is still sent with every HTTP request — the browser handles this transparently. The only thing prevented is JavaScript read access. There is almost never a legitimate reason for client-side JavaScript to read a session cookie. Set HttpOnly on all session cookies, always.
Secure
The Secure flag instructs the browser to only send the cookie over HTTPS connections, never over plain HTTP. Without it, a user on an HTTP connection (or a network where an attacker can strip HTTPS) would have their session cookie transmitted in plaintext and captured trivially with a network sniffer.
Set-Cookie: sessionid=abc123; HttpOnly; Secure
If your application enforces HTTPS (it should), set Secure on all cookies that contain sensitive data.
SameSite
The SameSite attribute controls when cookies are sent in cross-site requests. This is the primary defense against Cross-Site Request Forgery (CSRF) attacks, where a malicious third-party site tricks a user's browser into making authenticated requests to your application.
Three values are available:
SameSite=Strict— Cookie is only sent for requests originating from your own site. A user clicking a link from an external site to your app will not have the cookie sent on that first navigation request (they will appear logged out until the page reloads). Maximum protection, slight UX friction.SameSite=Lax— Cookie is sent on top-level navigations (e.g., clicking a link) but not on subresource requests from external sites. This is the browser default for cookies without an explicit SameSite attribute. Good balance of security and usability.SameSite=None— Cookie is sent in all cross-origin requests. RequiresSecureflag. Only use this for cookies that legitimately need to be sent cross-site (e.g., embedded widgets, OAuth flows).
Set-Cookie: sessionid=abc123; HttpOnly; Secure; SameSite=Lax
Domain and Path Scoping
Limit cookies to the narrowest scope they need:
- Omit the
Domainattribute unless you need the cookie shared across subdomains. SettingDomain=yoursite.comsends the cookie to all subdomains — including potentially less-secure ones. - Use
Path=/for application-wide session cookies. For cookies only needed by a specific section of your app, scope them to that path.
Complete Secure Session Cookie Example
Set-Cookie: sessionid=abc123xyz; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=86400
Framework-Specific Configuration
Flask (Python):
app.config.update(
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SECURE=True, # Only over HTTPS
SESSION_COOKIE_SAMESITE='Lax',
SESSION_COOKIE_NAME='session',
PERMANENT_SESSION_LIFETIME=86400 # 24 hours
)
Django:
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
Express (Node.js):
app.use(session({
secret: process.env.SESSION_SECRET,
cookie: {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 86400000 // 24 hours in ms
}
}));
What to Avoid
- Storing sensitive data in cookies: Cookies are sent with every request and can be read by JavaScript if HttpOnly is not set. Store a session ID, not the user's email, role, or permissions directly in a cookie.
- Long-lived session cookies without rotation: Rotate the session token after login, after privilege escalation, and periodically during long sessions.
- Predictable session tokens: Use a cryptographically secure random generator. Never base session tokens on user IDs, timestamps, or sequential numbers.
- Not invalidating sessions on logout: Deleting the client-side cookie is not enough. The server must also invalidate the session token so it cannot be reused by an attacker who captured it.
Checking Your Cookies
Inspect the cookies your application sets:
curl -sI https://yoursite.com/login -X POST -d "username=test&password=test" | grep -i "set-cookie"
Every Set-Cookie header for a session cookie should show HttpOnly, Secure, and SameSite. If any flag is missing, it is a finding. Automated scanners check all three on every cookie they observe in responses.