Session
A session is a server-side store of user-specific data (login state, preferences, cart contents) tied to an identifier stored in a cookie. Sessions maintain state across multiple stateless HTTP requests.
Explanation
HTTP is stateless — each request is independent, with no memory of previous requests. Sessions are the mechanism to bridge this gap: when a user logs in, the server creates a session object (containing user ID, roles, and other state), stores it server-side (in memory, a database, or Redis), and sends the client a session ID cookie. On every subsequent request, the client sends that cookie, the server looks up the session by ID, and has full access to the user's state. Session storage options: in-memory (fast, but lost on server restart, doesn't scale horizontally — all requests must hit the same server), database (persistent, scalable, but adds a DB lookup to every request), Redis (in-memory key-value store — fast, persistent, scales horizontally, the industry standard for session storage in production web apps). Session vs. JWT: sessions store state server-side (server holds the source of truth; sessions can be invalidated instantly). JWTs are stateless tokens containing claims (stored client-side in a cookie or localStorage; the server verifies the signature without a database lookup). Sessions are simpler and more secure for traditional web apps; JWTs scale better for microservices and stateless APIs but require careful handling of revocation (you can't invalidate a JWT before it expires without a server-side denylist, negating the stateless benefit). Session hijacking is the attack where an attacker steals a session ID (via XSS, network sniffing, or log exposure) and uses it to impersonate the victim. Mitigation: HttpOnly and Secure cookies, session regeneration on login, HTTPS only, and short session expiry for sensitive operations.
Code Example
javascript// Express session setup with Redis store
const express = require('express');
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const { createClient } = require('redis');
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
app.use(session({
store: new RedisStore({ client: redis }),
secret: process.env.SESSION_SECRET, // sign the session ID cookie
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 24 * 60 * 60 * 1000, // 24 hours
},
}));
// Login: create session
app.post('/login', async (req, res) => {
const user = await authenticate(req.body);
req.session.userId = user.id;
req.session.role = user.role;
res.json({ message: 'Logged in' });
});
// Protected route: check session
app.get('/profile', (req, res) => {
if (!req.session.userId) return res.status(401).json({ error: 'Not logged in' });
res.json({ userId: req.session.userId });
});
// Logout: destroy session
app.post('/logout', (req, res) => {
req.session.destroy(() => res.clearCookie('connect.sid').json({ ok: true }));
});
Why It Matters for Engineers
Sessions are one of the two main authentication patterns in web development (the other being JWTs). Choosing the right one — and understanding the trade-offs — is a foundational backend decision. Many AI-generated auth implementations default to JWTs without considering whether stateless auth is actually needed, introducing token revocation complexity unnecessarily. Understanding sessions also explains what "the server is stateful" means and why horizontal scaling (adding more servers) is harder when state is tied to a specific server instance. This is a core distributed systems trade-off that appears in system design interviews and real architecture decisions.
Related Terms
Cookies · JWT · HTTP · Middleware
Learn This In Practice
Go deeper with the full module on Beyond Vibe Code.
Web Development Fundamentals → →