Cookie security is often misunderstood, but at its core, it’s about preventing the browser from accidentally handing sensitive information to the wrong party.

Let’s see it in action. Imagine a simple web application that logs a user in and stores their session ID in a cookie.

HTTP/1.1 200 OK
Set-Cookie: sessionid=abcdef123456; Path=/; HttpOnly; Secure; SameSite=Lax
Content-Type: text/html
...

Here, sessionid=abcdef123456 is the actual data. The flags HttpOnly, Secure, and SameSite are the security guards around that data.

The problem cookie security solves is that browsers, by default, are a bit too eager to share cookies. Without proper security flags, a malicious script on a different site could potentially steal your session cookie, impersonate you, and access your account. This is the classic Cross-Site Scripting (XSS) attack vector.

The HttpOnly flag is the first line of defense. When a cookie has this flag set, it tells the browser: "Hey, only send this cookie over HTTP(S). Don’t let JavaScript get its hands on it." This is crucial because XSS attacks often rely on JavaScript to read cookies from document.cookie.

// This code will NOT be able to read the cookie if HttpOnly is set
let userSession = document.cookie;
console.log(userSession);

If HttpOnly is not set, the above JavaScript would happily log out the sessionid. With HttpOnly set, document.cookie would return an empty string or just other non-HttpOnly cookies. This directly prevents client-side scripts from accessing the cookie, thereby thwarting many common XSS attacks that aim to steal session tokens.

Next up is the Secure flag. This flag is straightforward: it tells the browser, "Only send this cookie over an encrypted HTTPS connection." If your site is running on http://example.com, a cookie with the Secure flag will never be sent by the browser. If it’s on https://example.com, the cookie will only be sent when the connection is indeed secure.

# If your server is only listening on HTTP, this cookie won't be sent
# If your server is on HTTPS, this cookie will ONLY be sent over HTTPS
Set-Cookie: user_preference=dark_mode; Secure

This prevents "man-in-the-middle" attacks where an attacker on the same network could intercept unencrypted HTTP traffic and read sensitive cookies. By enforcing HTTPS, you ensure the cookie’s journey from the browser to the server is protected by TLS/SSL encryption.

The SameSite flag is the most recent and perhaps the most complex of the three. It’s designed to combat Cross-Site Request Forgery (CSRF) attacks. CSRF happens when a malicious website tricks a user’s browser into making an unwanted request to a trusted site where the user is authenticated.

The SameSite flag has three possible values:

  • Strict: The browser will only send the cookie if the request originates from the exact same site as the cookie. If you click a link on evil.com that points to yourbank.com, the yourbank.com cookie with SameSite=Strict will not be sent. This is the most secure but can break legitimate cross-site navigation.
  • Lax (the default in many modern browsers): The browser sends the cookie on same-site requests and also for top-level navigations (like clicking a link) using "safe" HTTP methods (GET, HEAD, OPTIONS, TRACE). This means if you’re on evil.com and click a link to yourbank.com, the yourbank.com cookie will be sent. However, if evil.com makes a POST request (e.g., via a hidden form) to yourbank.com, the cookie won’t be sent. This provides a good balance between security and usability.
  • None: The browser will send the cookie with all cross-site requests. This is necessary for certain cross-origin scenarios (like embedding content from another site that needs to authenticate), but it offers no CSRF protection. If you use SameSite=None, you must also set the Secure flag, otherwise, it’s a security risk.

Consider this scenario: You’re logged into yourbank.com. You then visit malicioussite.com. malicioussite.com has a hidden form that automatically submits a POST request to yourbank.com/transfer. If yourbank.com’s session cookie does not have SameSite=Strict or Lax, the browser will send the cookie along with the POST request, and yourbank.com will think it’s a legitimate request from you. With SameSite=Lax or Strict, this malicious POST request would not include the cookie, and the transfer would fail because the server wouldn’t know who was making the request.

The interaction between these flags is key. A common configuration for sensitive session cookies is HttpOnly; Secure; SameSite=Lax. This means the cookie is protected from JavaScript, only sent over HTTPS, and generally not sent on cross-site requests, with exceptions for safe top-level navigation.

If you’re dealing with older browsers or need cookies to be accessible by JavaScript (which is generally discouraged for sensitive data), you might omit HttpOnly. If you absolutely need a cookie to be sent with any cross-site request, you’d use SameSite=None; Secure.

These flags are not just theoretical; they are critical controls that the browser enforces. Misconfiguring them, or omitting them entirely, leaves your application vulnerable.

The next layer of defense you’ll encounter involves managing cookie expiration and using more advanced techniques like content security policy to further restrict where scripts can execute.

Want structured learning?

Take the full Cryptography course →