Sessions vs Tokens
Part of the Full Stack Developer Roadmap
In short
HTTP forgets you after every request. After login, your app must recognise the same person on the next click. Sessions and tokens are the two main ways, and each changes how your backend is built.
The two dominant approaches to remembering a logged-in user — server-side sessions (stateful, stored in a DB/Redis) vs tokens like JWT (stateless, verified without a lookup) — each with different trade-offs for a full-stack app.

Think of it like this
Gate register vs signed gate pass
At one library, the guard writes your name in a register and checks it each time you return. At another, the principal gives you a signed, dated gate pass, and any guard can trust the signature without a register. Register is a session. Signed pass is a token.
Watch it happen
Remembering a logged-in user
Session
- Server stores session in DB or Redis
- Browser keeps only a random ID cookie
- Logout deletes the row, instantly
Token (JWT)
- Signed token carries user id and expiry
- Server checks signature, no lookup needed
- Hard to cancel before it expires
For one web app with its own backend, sessions in httpOnly cookies are simpler and easier to revoke. JWTs help when many services or mobile apps must verify users.
The big ideas
What you need to know
Cookies are the transport
Both can travel in a cookie. httpOnly stops JavaScript reading it, which protects you if an XSS bug sneaks in.
Short-lived tokens
Keep JWTs short, around 15 minutes, and use a refresh token stored server-side to get new ones.
Avoid localStorage for auth
Any script on the page can read localStorage. A single XSS bug would leak every user's token.
JWTs aren't secret
The payload is only base64 encoded. Anyone can decode it, so never put passwords or private data in it.
See it in code
Walk through the code
1// Option A: session (express-session + a store like Redis)2app.use(session({3 secret: process.env.SESSION_SECRET!, resave: false, saveUninitialized: false,4 cookie: { httpOnly: true, secure: true, sameSite: "lax", maxAge: 7 * 24 * 3600 * 1000 },5}));6app.post("/login", async (req, res) => {7 const user = await checkPassword(req.body.email, req.body.password);8 req.session.userId = user.id; // stored on the server, browser gets a session ID cookie9 res.sendStatus(204);10});11 12// Option B: JWT (jsonwebtoken)13const token = jwt.sign({ sub: user.id }, process.env.JWT_SECRET!, { expiresIn: "15m" });14res.cookie("token", token, { httpOnly: true, secure: true, sameSite: "lax" });15// later, on each request:16const payload = jwt.verify(req.cookies.token, process.env.JWT_SECRET!);1/4Session middleware with a secure, httpOnly cookie that JavaScript can't read.
Both send a cookie. The difference is where the truth lives: in your session store, or inside the signed token.
Save yourself the pain
Common mistakes
Oops
Storing the JWT in localStorage because a tutorial did it.
Do this instead
Prefer an httpOnly, Secure, SameSite cookie, so page scripts can't read it.
Oops
Issuing JWTs that last 30 days with no way to revoke them.
Do this instead
Use short access tokens plus a refresh token you can delete, or simply use sessions.
Your mission
Build both and compare
Quick check
A user's laptop is stolen. With which approach can you log them out everywhere right now?
Go deeper
- 1Implement a simple session-based login, then a token-based one, and compare the code.
- 2Learn why token-based auth is often preferred for APIs consumed by separate frontend apps.
- 3Understand sessions need shared storage (Redis) once you run multiple backend instances.
More topics at this stop
Stuck on this topic? Ask an Insider
Get 1:1 guidance from people who've walked this exact path — on the InsideEdge app.