Working with Sessions

1. Installing express-session

PackagePurpose
express-sessionCore session middleware
connect-redisRedis session store
connect-mongoMongoDB session store
session-file-storeFilesystem 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

FormBehavior
Single stringSign cookie
Array of stringsFirst signs, all verify (key rotation)
SourceMUST come from env, never hard-coded
Length≥ 32 random bytes (use crypto.randomBytes(32).toString("hex"))

4. Configuring Session Store

StoreUse Case
MemoryStore (default)Dev only — leaks & doesn't scale
RedisMulti-process, fast TTL
MongoDBAlready 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 });
  });
});
APIPurpose
req.session.key = valSet
req.session.save(cb)Force write to store
req.session.touch()Reset maxAge (rolling)
req.session.cookiePer-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.
OptionRecommended
httpOnlytrue
securetrue (prod)
sameSite"lax" (or "strict" for admin)
maxAgeHours, not days, for sensitive apps
nameCustom (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

TypeApproach
Absolute timeoutStore createdAt; reject if too old
Idle timeoutrolling: true resets maxAge on each request
Force expirereq.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();
});