`express-session` Plus a Real Session Store
Sessions
Build a session-based auth flow. Cookies in, sessions in Redis, user attached to req.
What you'll learn
- Configure express-session
- Use a Redis store
- Implement login / logout
express-session is the classic middleware. Store sessions in
Redis (or another shared store), put the session ID in a cookie.
Install
npm install express-session connect-redis ioredis Configure
import express from "express";
import session from "express-session";
import { RedisStore } from "connect-redis";
import { Redis } from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
app.use(session({
store: new RedisStore({ client: redis }),
secret: process.env.SESSION_SECRET, // never commit
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === "production", // HTTPS only in prod
sameSite: "lax",
maxAge: 24 * 60 * 60 * 1000,
},
})); That’s the entire auth infrastructure.
Login
import bcrypt from "bcrypt";
app.post("/auth/login", validate({ body: LoginSchema }), async (req, res) => {
const { email, password } = req.validBody;
const user = await db.users.findByEmail(email);
if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
return res.status(401).json({ error: { code: "invalid_credentials" } });
}
// store user id on the session — that's the whole login
req.session.userId = user.id;
res.json({ data: { id: user.id, email: user.email } });
}); Auth Middleware
export async function requireAuth(req, res, next) {
if (!req.session.userId) {
return res.status(401).json({ error: { code: "not_authenticated" } });
}
req.user = await db.users.findById(req.session.userId);
if (!req.user) {
req.session.destroy(() => {});
return res.status(401).json({ error: { code: "not_authenticated" } });
}
next();
}
app.get("/me", requireAuth, (req, res) => {
res.json({ data: req.user });
}); Logout
app.post("/auth/logout", (req, res) => {
req.session.destroy((err) => {
if (err) return res.status(500).end();
res.clearCookie("connect.sid"); // default cookie name
res.status(204).end();
});
}); Cookie Security
The three cookie flags everyone gets wrong:
httpOnly: true— JS can’t read it (blocks XSS exfiltration)secure: true— only sent over HTTPS (always on in production)sameSite: "lax"— sent on same-site GETs; blocks most CSRF
In production: all three. In development with HTTPS off: secure: false (or you’ll spend hours wondering why the cookie won’t stick).
Store
MemoryStore (the default) is dev-only — sessions die when the
process restarts. Always use a real store in production:
Redis, Postgres, MongoDB. connect-redis is by far the most
popular.
Session vs JWT — When To Pick This
Sessions shine when:
- You’re already using Redis
- Revocation matters (banning a user, password change, logout-all-devices)
- You want simple, well-understood crypto