Cookie Attributes (HttpOnly, Secure, SameSite)

Level 9 — Browser APIs (Storage & State) The flags that make cookies safe for auth.


1. Prerequisites


2. Term Category

Security (Universal: Configured on backend application server response headers and processed by client-side browser engines.): Cookie Attributes (HttpOnly, Secure, SameSite) is a fundamental concept in this technology stack. Level 9 — Browser APIs (Storage & State)


3. Explanation

(1) Design Motivation — "Why did we design this?"

By default, cookies are simple key-value strings stored in the browser and sent automatically with every matching HTTP request. However, when cookies store session IDs or JWTs, they become high-value targets for attackers:

  • XSS Attacks: If an attacker injects malicious JavaScript into your site, they can access document.cookie to steal authentication tokens.
  • CSRF Attacks: If a user visits a malicious site, that site can trigger background API calls to your server. The browser automatically appends your site's cookies to the request, executing actions on behalf of the user.

To protect authentication state, servers configure Cookie Attributes inside the Set-Cookie header to restrict when cookies can be accessed and sent:

1. HttpOnly

  • Behavior: Prevents client-side JavaScript from reading or writing the cookie (e.g. document.cookie will not show it).
  • Defense: Protects the cookie from theft via XSS (Cross-Site Scripting).

2. Secure

  • Behavior: Restricts the browser to sending the cookie only over encrypted connections (HTTPS). If you query the site over plain HTTP, the browser blocks the cookie.
  • Defense: Prevents cookie theft via packet sniffing (Man-in-the-Middle attacks) on public networks.

3. SameSite

  • Behavior: Restricts when cookies are sent along with cross-site requests (e.g. links from external sites).
  • Defense: The primary browser defense against CSRF (Cross-Site Request Forgery).
  • Values:
    • SameSite=Strict: The cookie is never sent on cross-site requests (e.g. even if you click a link from Google to your bank, you will load the page logged out).
    • SameSite=Lax (Default in modern browsers): The cookie is sent on cross-site requests only during safe, top-level navigations (e.g. clicking a regular <a> link), but blocked on background requests (like <img> source loads or fetch() calls from external pages).
    • SameSite=None: The cookie is sent on all cross-site requests. Requires the Secure attribute to be set.

(2) Reality Metaphor

Imagine carrying a physical security badge in your pocket.

  • Default Cookie: A plain badge. Anyone looking over your shoulder can read it (XSS), and if a stranger grabs your arm and pushes you into a building gate, the usher sees the badge in your pocket and lets you in (CSRF).
  • HttpOnly: Placing the badge inside a locked steel box in your pocket. You cannot take it out to read or show it to anyone on the street, but when you stand at the official gate (making a network request), a scanner reads the badge directly through the box steel.
  • Secure: The badge is made of photo-luminescent material that only works under secure blue lighting (HTTPS). If someone tries to inspect it under standard street lights (HTTP), it appears blank.
  • SameSite=Strict: You only show your badge if you walked directly from your own home to the office. If a tour guide from another company led you to the gate, you refuse to show it.

(3) HTTP Configuration Example

The backend server sets these attributes within the Set-Cookie response header:

HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: session_id=xyz987654321; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=86400

4. Common Mistakes & Pitfalls

Mistake 1: Assuming HttpOnly protects against CSRF attacks

The mistake: Believing that because a cookie has the HttpOnly attribute enabled, it is safe from Cross-Site Request Forgery.

Why it's wrong: HttpOnly only prevents JavaScript from reading the cookie values. However, if a malicious website triggers a background POST request to your API, the browser still appends the cookie automatically to that request. The server will see the valid session cookie and process the request, completing the CSRF attack.

Fix: You must combine HttpOnly with the SameSite attribute or use anti-CSRF tokens.


Mistake 2: Omitting Secure Flag on Production Authentication Cookies

The mistake: Setting Set-Cookie: session=abc123 without the Secure flag on production HTTPS sites.

Why it's wrong: Without Secure, browsers will send the session cookie over unencrypted HTTP requests if a user visits http://yourdomain.com, exposing session tokens to network sniffing.

Incorrect:

Set-Cookie: sid=abc123; HttpOnly ; ❌ Missing Secure flag on HTTPS site!

Fix:

Set-Cookie: sid=abc123; HttpOnly; Secure; SameSite=Strict

Mistake 3: Setting SameSite=None Without the Secure Flag

The mistake: Writing Set-Cookie: token=xyz; SameSite=None without adding Secure.

Why it's wrong: Modern browsers reject SameSite=None cookies unless accompanied by the Secure attribute flag.

Incorrect:

Set-Cookie: token=xyz; SameSite=None ; ❌ Rejected by modern browsers!

Fix:

Set-Cookie: token=xyz; SameSite=None; Secure

5. Practice Exercises

Scenario: An authentication server constructs RFC 6265 compliant Set-Cookie response headers with strict security flags.

Requirements:

  1. Write buildSecureCookieHeader(name, value, options).
  2. Support HttpOnly, Secure, SameSite, Path, Max-Age.
Answer

Implementation

function buildSecureCookieHeader(name, value, options = {}) {
  if (!name || value === undefined) throw new Error("Cookie name and value required");

  const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];

  if (options.maxAgeSeconds !== undefined) {
    parts.push(`Max-Age=${options.maxAgeSeconds}`);
  }
  if (options.domain) {
    parts.push(`Domain=${options.domain}`);
  }

  parts.push(`Path=${options.path || "/"}`);

  if (options.sameSite) {
    const ss = options.sameSite.toLowerCase();
    const formattedSs = ss === "strict" ? "Strict" : ss === "lax" ? "Lax" : "None";
    parts.push(`SameSite=${formattedSs}`);
  } else {
    parts.push("SameSite=Lax");
  }

  if (options.httpOnly !== false) {
    parts.push("HttpOnly");
  }
  if (options.secure !== false) {
    parts.push("Secure");
  }

  return parts.join("; ");
}

// Verification tests
const header = buildSecureCookieHeader("session_id", "xyz123", {
  maxAgeSeconds: 3600,
  sameSite: "strict"
});

console.assert(header.includes("session_id=xyz123"), "Test 1 Failed");
console.assert(header.includes("HttpOnly"), "Test 2 Failed");
console.assert(header.includes("Secure"), "Test 3 Failed");
console.assert(header.includes("SameSite=Strict"), "Test 4 Failed");

Technical Explanation

  1. HttpOnly Attribute: Prevents client-side JavaScript (document.cookie) from accessing the cookie, mitigating XSS attacks.
  2. Secure Attribute: Ensures cookie is ONLY transmitted over encrypted HTTPS connections.
  3. SameSite Attribute: Controls cross-site cookie transmission (Strict, Lax, None) to prevent CSRF attacks.

Exercise 2: SameSite Anti-CSRF Policy Auditor

Scenario: An API security linter evaluates cookie attributes to ensure session cookies are protected against Cross-Site Request Forgery.

Requirements:

  1. Write auditSameSitePolicy(cookieHeaderStr).
  2. Check SameSite=Strict/Lax.
  3. Check HttpOnly and Secure.
Answer

Implementation

function auditSameSitePolicy(cookieHeaderStr) {
  if (!cookieHeaderStr || typeof cookieHeaderStr !== "string") {
    return { secure: false, risks: ["Missing Set-Cookie header"] };
  }

  const parts = cookieHeaderStr.split(";").map(p => p.trim().toLowerCase());
  const risks = [];

  const hasHttpOnly = parts.includes("httponly");
  const hasSecure = parts.includes("secure");

  let sameSiteValue = "none";
  for (const part of parts) {
    if (part.startsWith("samesite=")) {
      sameSiteValue = part.split("=")[1];
    }
  }

  if (!hasHttpOnly) risks.push("Cookie vulnerable to XSS theft (missing HttpOnly)");
  if (!hasSecure) risks.push("Cookie sent over unencrypted HTTP (missing Secure)");
  if (sameSiteValue === "none") risks.push("Cookie vulnerable to CSRF attacks (SameSite=None)");

  return {
    secure: risks.length === 0,
    sameSiteValue,
    risks
  };
}

// Verification tests
const weakCookie = "session=123; Path=/; SameSite=None";
const audit = auditSameSitePolicy(weakCookie);

console.assert(audit.secure === false, "Test 1 Failed");
console.assert(audit.risks.length === 3, "Test 2 Failed: Identifies XSS, HTTPS, and CSRF risks");

Technical Explanation

  1. SameSite=Strict: Cookie is NEVER sent in cross-site requests (e.g. following external links).
  2. SameSite=Lax: Default browser policy: cookie sent on top-level GET navigation from external sites, but withheld on cross-site POSTs.
  3. SameSite=None Requirement: SameSite=None MUST be paired with Secure attribute (SameSite=None; Secure).

Scenario: Converts modern Max-Age (delta seconds) into UTC Expires header date strings (Wdy, DD-Mon-YYYY HH:MM:SS GMT).

Requirements:

  1. Write maxAgeToExpiresString(maxAgeSeconds).
  2. Return formatted UTC HTTP date string.
Answer

Implementation

function maxAgeToExpiresString(maxAgeSeconds) {
  if (typeof maxAgeSeconds !== "number" || maxAgeSeconds < 0) {
    return "Expires=Thu, 01 Jan 1970 00:00:00 GMT";
  }

  const expiryDate = new Date(Date.now() + maxAgeSeconds * 1000);
  return `Expires=${expiryDate.toUTCString()}`;
}

// Verification tests
const expiresStr = maxAgeToExpiresString(3600);
console.assert(expiresStr.startsWith("Expires="), "Test 1 Failed");
console.assert(expiresStr.includes("GMT"), "Test 2 Failed");

const deleteStr = maxAgeToExpiresString(-1);
console.assert(deleteStr.includes("1970"), "Test 3 Failed: Negative Max-Age sets 1970 deletion date");

Technical Explanation

  1. Max-Age vs Expires: Max-Age specifies relative lifetime in seconds; Expires specifies absolute UTC date.
  2. Max-Age Precedence: If both attributes are present, modern browsers prioritize Max-Age over Expires.
  3. Deleting Cookies: Setting Max-Age=0 or an Expires date in the past immediately purges the cookie from browser storage.


7. Key Takeaways

  • Cookie attributes configure security parameters directly inside Set-Cookie headers.
  • HttpOnly blocks JavaScript access, protecting cookies from XSS script theft.
  • Secure restricts cookie transmission to encrypted HTTPS connections.
  • SameSite controls cookie sending on cross-site requests, mitigating CSRF attacks.
  • Lax is the browser default, allowing cookie sends only on top-level safe navigations.
  • HttpOnly does not defend against CSRF; it must be paired with SameSite configurations.
Built with LogoFlowershow