Skip to content

Session Authentication in Express

Session-based authentication is the classic approach used by traditional server-rendered web applications, storing session data server-side and identifying the client through a cookie. This lesson covers implementing it with express-session.

How Session Authentication Works

When a user logs in, the server creates a session record (containing, for example, their user ID) and stores it in a session store, memory by default, but Redis or a database in production. The client receives a cookie containing only an opaque session ID, never the actual session data.

On each subsequent request, Express reads the session ID from the cookie, looks up the corresponding session data from the store, and exposes it as req.session.

const session = require('express-session');

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: { httpOnly: true, maxAge: 24 * 60 * 60 * 1000 },
}));

app.post('/login', async (req, res) => {
  const user = await verifyCredentials(req.body.email, req.body.password);
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });

  req.session.userId = user.id;
  res.json({ loggedIn: true });
});

req.session is automatically persisted between requests once express-session middleware is registered.

Reading and Destroying the Session

app.get('/me', (req, res) => {
  if (!req.session.userId) return res.status(401).json({ error: 'Not logged in' });
  res.json({ userId: req.session.userId });
});

app.post('/logout', (req, res) => {
  req.session.destroy(() => res.json({ loggedOut: true }));
});
  • req.session behaves like a plain object, attach whatever data the request cycle needs.
  • req.session.destroy() removes the session from the store entirely.
  • The default MemoryStore is for development only, it leaks memory and doesn't scale across multiple server processes.
  • Always set httpOnly: true (and secure: true in production) on the session cookie.

Session Authentication Cheatsheet

The express-session configuration and usage you will need most.

Task Code
Install npm install express-session
Register app.use(session({ secret, cookie: {...} }))
Set session data req.session.userId = user.id
Read session data req.session.userId
Destroy session req.session.destroy(callback)
Production store connect-redis, connect-mongo, etc.

Using a Real Session Store in Production

The default in-memory store is explicitly documented as unsuitable for production, it never expires old sessions and can't be shared across multiple server instances behind a load balancer. Use connect-redis (or a similar store) instead.

const RedisStore = require('connect-redis').default;
const { createClient } = require('redis');

const redisClient = createClient({ url: process.env.REDIS_URL });
redisClient.connect();

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
}));

Protecting Routes With Sessions

A simple middleware checks for a logged-in session before allowing access to protected routes, mirroring the JWT authenticate middleware but checking req.session instead.

function requireLogin(req, res, next) {
  if (!req.session.userId) {
    return res.status(401).json({ error: 'Not logged in' });
  }
  next();
}

app.get('/dashboard', requireLogin, dashboardHandler);

Common Mistakes

  • Using the default MemoryStore in production, causing memory leaks and broken multi-server deployments.
  • Forgetting httpOnly and secure on the session cookie, weakening protection against XSS and network sniffing.
  • Not calling req.session.destroy() on logout, leaving stale sessions active.
  • Storing large or sensitive objects directly in the session instead of just an identifier.

Key Takeaways

  • Session authentication stores session data server-side, identified by an opaque cookie value.
  • express-session manages reading, writing, and persisting req.session automatically.
  • Always replace the default memory store with Redis or another production-grade store.
  • Session cookies should always be httpOnly, and secure in production.

Pro Tip

If you deploy behind a load balancer with multiple server instances, a shared session store (Redis) is not optional, without it users will be randomly logged out whenever their request lands on a different instance.