Overview
A session keeps a user logged in across stateless HTTP requests. After login, the server creates a session record (user ID, expiry, metadata) in a store and gives the browser a random, unguessable session ID in a cookie. Every later request sends the cookie, and the server looks up the session to identify the user.
Server-side sessions are simple and secure: revoking access is as easy as deleting the record. To scale horizontally, sessions must live in a shared store like Redis rather than in one server's memory, otherwise users are logged out whenever the load balancer sends them to another server.
You get a ticket with a random number. The ticket itself reveals nothing, but the attendant looks up the number to find your coat. If the attendant voids the ticket, it no longer works.
When to use it
- Traditional web applications with browser clients.
- When instant logout and revocation matter (banking, admin tools).
- When you want minimal client-side token handling.
- Behind-the-scenes backend-for-frontend patterns that hide tokens from browsers.
Where it shows up in interviews
Recognize it when: scale web servers horizontally without sticky sessions.
- Design a scalable web app
- Design an e-commerce checkout
Recognize it when: millions of logged-in users, logout everywhere.
- Design Netflix account sessions
- Design device management for logins
Where it is used in real software
Frameworks like express-session, Spring Session, and Django use Redis to share sessions across instances with TTL expiry.
Services like Google and Netflix list active sessions per user and can revoke them individually.
Frameworks regenerate the session ID at login so an attacker cannot plant a known ID before the user signs in.
Key terms
- Session ID
- A long random identifier (128+ bits) stored in a cookie.
- Session store
- Where session data lives: Redis, database, or memory.
- Idle timeout
- Expire after inactivity (for example 30 minutes).
- Absolute timeout
- Expire after a maximum age regardless of activity.
- Sticky sessions
- Load balancer pins a user to one server; an alternative to a shared store with downsides.
How it works, step by step
- 1User logs in
Credentials verified.
- 2Create the session
Generate a random ID; store { userId, createdAt, lastSeen } in Redis with a TTL.
- 3Set the cookie
Set-Cookie: sid=...; HttpOnly; Secure; SameSite=Lax.
- 4Each request
Read the cookie, look up the session, attach the user, and refresh the idle TTL.
- 5Logout or expiry
Delete the session record; the cookie becomes useless.
Where to store sessions
Choice affects scaling and availability
| Store | Scales horizontally | Survives restarts | Notes |
|---|---|---|---|
| Server memory | No (needs sticky sessions) | No | Fine for a single server |
| Redis | Yes | With persistence / replicas | Most common; TTL built in |
| Database table | Yes | Yes | Slower; cleanup jobs needed |
| Signed cookie | Yes (no store) | Yes | Size limits; revocation is hard |
NOWStore: Server memory | Scales horizontally: No (needs sticky sessions) | Survives restarts: No | Notes: Fine for a single server
Redis is the default: fast lookups, automatic TTL expiry, and shared by all app instances.
Implementation
import session from "express-session";import { RedisStore } from "connect-redis";import { createClient } from "redis"; const redis = createClient({ url: process.env.REDIS_URL });await redis.connect(); app.use(session({ store: new RedisStore({ client: redis, prefix: "sess:" }), name: "sid", secret: process.env.SESSION_SECRET!, resave: false, saveUninitialized: false, rolling: true, // refresh idle expiry on activity cookie: { httpOnly: true, secure: true, sameSite: "lax", maxAge: 30 * 60 * 1000 },})); app.post("/login", async (req, res, next) => { const user = await verifyCredentials(req.body.email, req.body.password); req.session.regenerate((err) => { // new ID after login prevents session fixation if (err) return next(err); req.session.userId = user.id; res.sendStatus(204); });}); app.post("/logout", (req, res) => req.session.destroy(() => res.clearCookie("sid").sendStatus(204)));Complexity and performance
Redis round trip.
10M sessions = a few GB.
Trade-offs
Sessions give instant revocation and tiny cookies at the cost of a store lookup; JWTs avoid the lookup but are hard to revoke.
Sticky sessions avoid a store but break balance and lose sessions when a server dies.
Variants and related techniques
The browser holds only a session cookie; the BFF stores OAuth tokens server-side and calls APIs.
Store device name and IP per session to show and revoke active logins.
Common mistakes
- Predictable session IDs.
Fix: Use a cryptographically secure random generator with at least 128 bits.
- Not regenerating the ID at login.
Fix: Prevents session fixation attacks.
- Missing cookie flags.
Fix: HttpOnly, Secure, and SameSite are required.
Interview questions
How do sessions work with multiple servers?
Store sessions in a shared store such as Redis so any server can look up any session ID; or use sticky sessions, which is simpler but hurts balancing and failover.
How would you implement 'log out of all devices'?
Index sessions by user ID (a Redis set of session IDs per user) and delete them all; for JWT-based systems, revoke all refresh tokens and bump a per-user token version checked on refresh.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Add Redis-backed sessions to a web app | Easy | Shared store. |
| Design active session management for a streaming service | Medium | Device list, limits, revocation. |