Working with Sessions
1. Installing express-session
| Package | Purpose |
express-session | Core session middleware |
connect-redis | Redis session store |
connect-mongo | MongoDB session store |
session-file-store | Filesystem store (dev only) |
2. Configuring Session Middleware
Example: Production session
import session from "express-session";
import RedisStore from "connect-redis";
import { createClient } from "redis";
const redis = createClient({ url: env.REDIS_URL });
await redis.connect();
app.set("trust proxy", 1); // required for secure cookies behind proxy
app.use(session({
store: new RedisStore({ client: redis, prefix: "sess:" }),
secret: env.SESSION_SECRET,
name: "sid",
resave: false,
saveUninitialized: false,
rolling: true,
cookie: {
httpOnly: true,
secure: true,
sameSite: "lax",
maxAge: 1000 * 60 * 60 * 8
}
}));
3. Setting Session Secret
| Form | Behavior |
| Single string | Sign cookie |
| Array of strings | First signs, all verify (key rotation) |
| Source | MUST come from env, never hard-coded |
| Length | ≥ 32 random bytes (use crypto.randomBytes(32).toString("hex")) |
4. Configuring Session Store
| Store | Use Case |
| MemoryStore (default) | Dev only — leaks & doesn't scale |
| Redis | Multi-process, fast TTL |
| MongoDB | Already using Mongo |
| SQL (connect-pg-simple) | Postgres environments |
Warning: The default MemoryStore is NOT production-safe. It leaks memory and doesn't share across processes/replicas.
5. Accessing Session Data
Example: req.session
app.get("/cart", (req, res) => {
res.json(req.session.cart || []);
});
6. Storing Session Variables
Example: Set + persist
app.post("/cart", (req, res) => {
req.session.cart = [...(req.session.cart || []), req.body.item];
req.session.save((err) => { // explicit save (rare but useful)
if (err) return next(err);
res.json({ items: req.session.cart.length });
});
});
| API | Purpose |
req.session.key = val | Set |
req.session.save(cb) | Force write to store |
req.session.touch() | Reset maxAge (rolling) |
req.session.cookie | Per-request cookie options |
7. Destroying Sessions
Example: Logout
app.post("/logout", (req, res, next) => {
req.session.destroy((err) => {
if (err) return next(err);
res.clearCookie("sid");
res.sendStatus(204);
});
});
8. Regenerating Session ID
Example: Prevent session fixation on login
app.post("/login", async (req, res, next) => {
const user = await authenticate(req.body);
if (!user) return res.sendStatus(401);
req.session.regenerate((err) => {
if (err) return next(err);
req.session.userId = user.id;
req.session.save(() => res.json({ ok: true }));
});
});
Note: Always regenerate the session ID after a privilege change (login, role escalation) to prevent session fixation attacks.
9. Setting Session Cookie Options
| Option | Recommended |
httpOnly | true |
secure | true (prod) |
sameSite | "lax" (or "strict" for admin) |
maxAge | Hours, not days, for sensitive apps |
name | Custom (e.g., "sid") — hides framework |
10. Using Session with Redis
Example: Redis store full setup
const redis = createClient({ url: env.REDIS_URL });
redis.on("error", (err) => logger.error({ err }, "redis error"));
await redis.connect();
app.use(session({
store: new RedisStore({ client: redis, prefix: "sess:", ttl: 28800 }),
secret: env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { secure: true, httpOnly: true, sameSite: "lax", maxAge: 28800000 }
}));
11. Implementing Session Timeout
| Type | Approach |
| Absolute timeout | Store createdAt; reject if too old |
| Idle timeout | rolling: true resets maxAge on each request |
| Force expire | req.session.cookie.maxAge = 0 |
Example: Idle + absolute
app.use((req, res, next) => {
if (req.session.userId) {
req.session.createdAt ??= Date.now();
if (Date.now() - req.session.createdAt > 8 * 60 * 60 * 1000) {
return req.session.destroy(() => res.sendStatus(401));
}
}
next();
});