The test of a session system is revocation
Anybody can issue a session. The question that separates designs is what happens when you need to end one immediately: a stolen laptop, a shared password, a member removed from a team, a support agent who has left, an account you have just banned.
If your answer is “they stay logged in until it expires”, you do not have a session system, you have a countdown.
Two designs, and the trade nobody explains clearly
Server-side sessions. The cookie holds a random opaque ID; the server looks it up in Redis or a table. Revoking is a delete. Reading costs one fast lookup per request. This is the boring design and it is the right default for almost every application, including large ones.
Stateless tokens (JWTs). The token itself carries the claims, signed. No lookup, which is the entire selling point, and therefore no way to revoke, because nothing is consulted. The usual patch is short-lived access tokens (5 to 15 minutes) plus a long-lived refresh token that is stored server-side, so revocation takes effect at the next refresh. That is a reasonable design, and notice what it has done: reintroduced the database lookup you were avoiding, and added a window during which a revoked user is still working.
Choose stateless tokens when you have several services that must verify without a shared store. Choose server-side sessions otherwise. “JWTs scale better” is a claim about an architecture most small products do not have, and the cost is paid in the incident where you cannot log somebody out.
The cookie attributes, and what each one prevents
Set-Cookie: sid=<random>; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=1209600- HttpOnly keeps JavaScript from reading it, so one cross-site scripting bug does not hand over every session.
- Secure keeps it off plain HTTP.
- SameSite=Lax stops the cookie being sent on most cross-site requests, which removes the majority of cross-site request forgery.
Strictbreaks inbound links from other sites;NonerequiresSecureand re-opens the risk, so use it only for a genuine cross-site flow. - A random, long ID: at least 128 bits from a cryptographic generator. Not a counter, not a hash of the user ID, not anything derived from time.
Store a hash of the session ID rather than the ID itself, so a leaked database is not a set of working sessions. This is the same argument as hashing passwords, applied to the thing that substitutes for one.
Rotate on privilege change
Issue a new session ID at login, and again whenever authority changes: password change, email change, second factor enabled, role granted. Two reasons: it defeats session fixation, where an attacker plants a known ID before you log in, and it ensures a password change actually ends the attacker’s session rather than leaving it alongside yours.
A password reset should, by default, end every other session. If you offer “log out everywhere”, make sure it reaches API tokens and mobile apps too, or it is a comforting button that does nothing.
Show people their sessions
A list of active sessions (device, approximate location, last seen, with a revoke button) is perhaps two hundred lines of work and it does three things at once: it lets a user end a session you know nothing about, it makes a compromise visible to the only person who can recognise it, and it gives your support process something to point at. Every large service has one because they are the first thing a worried user asks for.
Expiry
There is a real tension. Short sessions are safer and annoy people into choosing worse passwords and staying signed in on shared machines. Long sessions are kind and leave more time for a stolen cookie to be useful.
A defensible middle: a sliding two-week session for ordinary use, an absolute maximum of a few months regardless of activity, and a re-authentication prompt in front of dangerous actions (changing email, deleting the account, viewing payment details) regardless of how fresh the session is. That last one is what protects the user whose unlocked laptop somebody borrowed for a minute.
The failure to plan for
Credential stuffing is the common attack and it is not a breach of your system: somebody takes a list of passwords leaked elsewhere and tries them against your login. The defences are rate limits per account rather than only per IP, a check against known-breached password lists (the free range-query API from Have I Been Pwned does this without sending you the password), and a second factor for accounts that can do damage.
When it succeeds, everything above is what you use: revoke the sessions, force a reset, show the user their session list, and tell them plainly what happened.
Every lesson here is open. Your progress is recorded once you pass the Agents and Automation exam.
Check your understanding
A service issues 24-hour JWTs with no server-side store. A user’s laptop is stolen and they ask to be logged out everywhere. What can the service actually do?
Pick the one you would defend. Your answer stays private.
Stuck on something?Ask Tri, your AI tutor