Authentication foundations

Choose server-side sessions

Use a random opaque cookie value that maps to server-side session state and understand the tradeoff against self-contained tokens.

For a browser application, my default is an opaque server-side session. The browser holds a random string that means nothing on its own. The server holds everything else: who the user is, when they logged in, when the session expires.

The cookie might contain something like 7xK...pQ. There’s no user data in there. On every request the server takes that value and looks up a record like this:

session hash → user ID, created time, last-used time, expiry, revoked time

If the record exists and is still valid, the request is authenticated. If not, it isn’t. That’s the whole mechanism.

Generating the identifier

Generate the identifier with a cryptographically secure random source, or let a trusted session library do it. In Node.js that’s crypto.randomBytes(32), not Math.random(). The value must be impossible to guess.

Then store only a hash of it, when your setup allows. The raw value lives in the cookie. When a request arrives, the server hashes the presented value and looks up the matching row. This is the same trick we’ll use for password-reset tokens later.

Why hash it? If someone dumps your sessions table, they get hashes. They can’t turn a hash back into a cookie value, so the leak alone doesn’t let them log in as anyone.

The cost and what you get back

Server-side sessions have a real cost. Every authenticated request needs a database or cache lookup, and every application instance must reach the same store.

In exchange you get immediate control. Logout works instantly. Suspending an account works instantly. Password reset can kill every other session. “Sign out everywhere” is a single DELETE query. Delete the row, and the cookie in the browser becomes a useless string.

What about self-contained tokens?

Tokens that carry their own claims, like a signed JWT, fit some architectures well. But they don’t remove the work. You still need key rotation, expiry, audience checks, a safe place to store them in the browser, and usually a revocation list, which brings the lookup back anyway.

My advice: choose self-contained tokens when your architecture needs their specific properties, for example many services that can’t share a session store. Don’t choose them to save one database query.

Try this on the Books API: design the users and sessions tables. Put no password, role, email, or personal data in the cookie value, only the random identifier. Then write down, step by step, what happens when you delete one row from sessions and the browser sends that cookie again. The request should fail with 401, and nothing else should change.

Lesson completed