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.
Set-Cookie header.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.
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 |
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 sample code above configures express-session with hardened defaults. The lifecycle rules around it matter as much as the attributes:
// Regenerate on privilege change (login, role switch): defeats session fixation
req.session.regenerate(cb);// Destroy on logout and clear the cookie
app.post("/logout", (req, res) => {
req.session.destroy(() => {
res.clearCookie("__Host-sid");
res.status(204).end();
});
});maxAge refreshed on activity) and an absolute timeout (for example 12 hours) limit how long a stolen cookie is useful.Referer header.Domain=example.com, which shares the cookie with every subdomain, including ones you do not control.secure: true in production because it was disabled for local HTTP development.Which attribute prevents JavaScript on the page from reading the session cookie?
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.Next lesson: Understanding SQL Injection and How to Prevent It — the classic injection attack and the parameterized queries that stop it.