Cookies and Session Management: HttpOnly, Secure and SameSite

Intermediate
12 min

Cookies and Session Management: HttpOnly, Secure and SameSite

HTTP is stateless, so a website remembers that you logged in by handing your browser a cookie containing a session identifier. Whoever holds that identifier is you as far as the server is concerned. Session management is therefore the art of making the identifier hard to steal, hard to forge, and short-lived.

In this lesson you will learn how server-side sessions work, what each cookie attribute protects against, how SameSite interacts with CSRF, and how to run a session lifecycle correctly in Express.

How a Session Works

  1. The user authenticates.
  2. The server generates a long random session ID and stores it in a session store (Redis, a database) along with the user ID and creation time.
  3. The ID is sent to the browser in a Set-Cookie header.
  4. Every later request carries the cookie; the server looks up the ID and knows who is talking.

Two properties follow: the ID must be unpredictable (128 bits or more from a cryptographic random source), and the server must be able to invalidate it at any time, the main advantage over stateless tokens.

Cookie Attributes and What They Prevent

http
Set-Cookie: __Host-sid=8f3a…; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=1800

| Attribute | Protects against | Notes | |---|---|---| | Secure | Sniffing over plain HTTP | Cookie is only sent over HTTPS | | HttpOnly | Theft via XSS (document.cookie) | Script cannot read it; it is still sent automatically | | SameSite | CSRF and cross-site leakage | Strict, Lax (default in modern browsers) or None | | Path / Domain | Over-sharing with subdomains | Omit Domain so only the exact host receives the cookie | | Max-Age / Expires | Sessions that never end | Omit for a session cookie, set for a bounded lifetime | | __Host- prefix | Cookie injection from subdomains | Browser enforces Secure, Path=/ and no Domain |

Understanding SameSite

SameSite decides whether the browser attaches the cookie to requests that originate from a different site (registrable domain, not origin: app.example.com and api.example.com are the same site).

  • Strict: never sent cross-site, even when the user clicks a link to your app from an email. Safest, but users land logged out.
  • Lax: sent on top-level navigations that use safe methods (a normal link click) but not on cross-site POST, fetch, iframes or images. This blocks classic CSRF while keeping links working.
  • None: always sent; requires Secure. Needed only for genuinely cross-site embeds such as a widget hosted on a partner domain.

Lax is the right default for a session cookie. Keep an anti-CSRF token as well for state-changing endpoints, because Lax still allows a cross-site top-level GET, and older clients may not enforce it.

The Session Lifecycle in Express

The sample code above configures express-session with hardened defaults. The lifecycle rules around it matter as much as the attributes:

javascript
// Regenerate on privilege change (login, role switch): defeats session fixation req.session.regenerate(cb);
javascript
// Destroy on logout and clear the cookie app.post("/logout", (req, res) => { req.session.destroy(() => { res.clearCookie("__Host-sid"); res.status(204).end(); }); });
  • Session fixation happens when an attacker plants a known session ID before login and the server keeps using it. Regenerating the ID after authentication closes the hole.
  • Idle timeout (maxAge refreshed on activity) and an absolute timeout (for example 12 hours) limit how long a stolen cookie is useful.
  • Invalidate all of a user's sessions when the password changes; a server-side store makes this a single delete.

Common Mistakes

  • Putting the session ID in the URL, where it leaks through logs, bookmarks and the Referer header.
  • Setting Domain=example.com, which shares the cookie with every subdomain, including ones you do not control.
  • Forgetting secure: true in production because it was disabled for local HTTP development.
  • Storing sensitive data in the cookie itself instead of the server-side session.
Quick Quiz
Question 1 of 2

Which attribute prevents JavaScript on the page from reading the session cookie?

Key Takeaways

  • A session ID is a bearer credential: long, random, stored server-side, and revocable.
  • Secure, HttpOnly and SameSite=Lax are the baseline attributes for every session cookie; the __Host- prefix locks them in.
  • SameSite applies per site, not per origin, and Lax blocks cross-site POSTs but not top-level GETs.
  • Regenerate the ID on login, destroy it on logout, and enforce idle and absolute timeouts.
  • Never put session IDs in URLs or sensitive data inside the cookie.

Next lesson: Understanding SQL Injection and How to Prevent It — the classic injection attack and the parameterized queries that stop it.

Cookies and Session Management: HttpOnly, Secure and SameSite - Cyber Security | CodeYourCraft | CodeYourCraft