Guide

HTTP has no memory. So after a user logs in, the server hands the browser a session: a random identifier stored in a cookie. The browser sends the cookie on every request, and the server looks up what that identifier means. The cookie is the key. Whoever holds it is logged in as that user.

That makes the cookie a bearer credential: whoever holds it is logged in.

Walking through it

A login response typically sets a cookie like this:

Set-Cookie: __Host-sid=q8V2m...43chars...; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=1209600

Each attribute stops one attack:

AttributeWhat it blocks
HttpOnlyScripts (document.cookie) can't read the value, which limits damage from XSS
SecureThe cookie is sent only over HTTPS, not in cleartext on the network
SameSite=Lax or StrictBrowsers don't attach the cookie to most cross-site requests, which cuts CSRF
__Host- prefixBrowsers accept the cookie only if it has Secure, Path=/ and no Domain, so a subdomain can't overwrite it
Max-AgeBounds how long a stolen cookie stays useful

Session fixation

Suppose the attacker plants a known session ID in a victim's browser, for example through a crafted link. If the server keeps that ID when the victim logs in, the attacker now holds a logged-in session.

Fix it by issuing a new session ID at every privilege change: login, password change, MFA step-up and role change. Discard the old ID. Most frameworks have a single call for this, so check that yours rotates the ID.

CSRF

Cross-site request forgery happens when a malicious page makes the victim's browser send a request to your site. Before SameSite became the default in browsers, the browser attached your cookie automatically, so the request ran as the victim.

Two defenses work together:

  1. SameSite=Lax or Strict on the session cookie. This is the first line.
  2. A CSRF token, a random value tied to the session and sent in a header or hidden form field. State-changing requests, such as POST, must include it. A forged page can't read it, so it can't add it.

Don't make state changes on GET. A cross-site image tag or link can trigger a GET even with SameSite.

Server-side sessions versus signed cookies

Two designs are common:

  • Server-side session. The cookie holds an opaque ID. The server stores the real state in Redis or a database. Logging out or revoking access is one delete.
  • Signed or encrypted cookie. The cookie holds the state, protected by an HMAC or an AEAD. Nothing is stored on the server, but you can't revoke a single session until it expires. Rotating the signing key logs out everyone.

Prefer server-side sessions for web apps that need instant revocation. Signed tokens suit APIs spread across services, with the trade-offs covered in JWTs and token validation.

Note

A stolen session cookie needs no password, so watch for session IDs in logs, URLs and error reports.

Common mistakes

  • Putting the session ID in a URL, where it lands in logs, browser history and Referer headers.
  • Keeping the same session ID after login.
  • Omitting HttpOnly or Secure because local testing over HTTP was painful.
  • Relying on SameSite alone for state changes that also need a CSRF token.

Check yourself

  • Why does rotating the session ID at login stop fixation? Answer: the attacker's planted ID is no longer valid after the victim's privilege change.
  • Why can't a server-side session be revoked when a signed cookie can? Answer: a signed cookie is valid until it expires, since the server stores nothing. A server-side session is one row to delete.

Key takeaways

  • A session cookie is a bearer credential. Set HttpOnly, Secure and SameSite, and use the __Host- prefix.
  • Issue a new session ID at every login and privilege change.
  • Use SameSite plus a CSRF token, and keep state changes off GET.
  • Choose server-side sessions when you need instant revocation.

Further learning

OWASP's Session Management Cheat Sheet covers these attributes and the attack paths in more detail.

OWASP Session Management Cheat SheetWebsite with the collection of all the cheat sheets of the project.cheatsheetseries.owasp.org