After someone logs in, how does the server remember them on the next request? HTTP is stateless — every request arrives with no memory of the last one. Two big approaches: server-side sessions and JWTs. This is a super common interview topic.
Session-cookie auth — server keeps the state
In simple language — the server remembers who’s logged in, and the browser just carries a ticket stub.
On login, the server creates a session (a record in memory, Redis, or a DB), generates a random session ID, and sends it back in a cookie. On every later request, the browser auto-sends that cookie, the server looks up the session, and knows who we are.
Set-Cookie: sid=8f3a...random...; HttpOnly; Secure; SameSite=Lax
The session ID is meaningless on its own — it’s just a lookup key. All the real data (user id, roles) lives on the server.
JWT — the token carries the state
A JWT (JSON Web Token) flips it around. Instead of storing state on the server, we pack the user info into the token itself, sign it, and hand it over. The server keeps nothing. That’s what “stateless” means.
On the next request, the client sends the token back (usually Authorization: Bearer ...). The server just verifies the signature — no database lookup needed. If the signature checks out, we trust the claims inside.
Think of it like the difference between a coat-check ticket (session ID — number means nothing without the server’s records) and a signed, tamper-proof concert wristband (JWT — everything’s printed right on it).
sid=8f3aBearer eyJ...JWT structure — three parts, dot-separated
A JWT is just three Base64URL chunks glued with dots: header.payload.signature.
{ "alg": "HS256", "typ": "JWT" }{ "sub": "7", "role": "admin", "exp": 1732 }HMAC(header + "." + payload, secret)eyJhbGc.eyJzdWI.4pWn8...Two things people always get wrong: the payload is Base64, not encrypted — anyone can decode and read it, so never put secrets in there. And the signature doesn’t hide anything; it just proves the token wasn’t tampered with, because only the server knows the signing secret.
The revocation problem
Here’s the JWT catch interviewers dig into. Because the server keeps no state, it can’t easily un-issue a token. If a token is stolen, or a user is banned, that JWT stays valid until it expires on its own. There’s no session to delete.
Workarounds:
- Short-lived access tokens (5–15 min) + a long-lived refresh token to get new ones. Small blast radius.
- A denylist of revoked token IDs — but now we’re keeping server state again, which defeats the “stateless” selling point.
Sessions don’t have this problem: delete the session row and the user is instantly out.
Where to store the token
This is the security part, and it’s all about picking your poison.
localStorage— easy, but readable by any JavaScript on the page. One XSS (cross-site scripting — attacker runs JS in our page) and the token is stolen.- Cookie with
HttpOnly— JS can’t read it, so XSS can’t grab it. But cookies auto-send on every request, which opens CSRF (cross-site request forgery — another site tricks the browser into sending our cookie). We defend that with theSameSiteattribute and CSRF tokens.
So the trade-off in one line: localStorage dodges CSRF but is exposed to XSS; HttpOnly cookies dodge XSS but need CSRF protection. The common recommendation leans toward HttpOnly; Secure; SameSite cookies.
Interview soundbite
Sessions store state on the server and send a random session ID in a cookie — easy to revoke, needs a lookup. JWTs are stateless: signed claims live in the token (header.payload.signature, Base64 not encrypted), so the server just verifies the signature — scales flat but is hard to revoke before expiry. Fix revocation with short access tokens plus refresh tokens. Store tokens in HttpOnly cookies to dodge XSS, then add SameSite/CSRF tokens for CSRF.