Session Management in MPAs β Cookies, Sessions, and User State
In this tutorial, you will learn about Session Management in MPAs. We cover key concepts, practical examples, and best practices to help you master this topic.
Session management in MPAs uses server-side sessions with cookies to store user state across HTTP requests, enabling authentication persistence, user preferences, and shopping cart data.
What You'll Learn
By the end of this tutorial, you will understand how sessions work in MPAs, the difference between cookie-based and server-side sessions, session storage options (in-memory, Redis, database), session security best practices, and how to manage user login state across page requests.
Why It Matters
HTTP is stateless β each request is independent. Without sessions, every page load would require the user to log in again. Sessions are the foundation of all authenticated experiences on the web, from e-commerce checkouts to personalized dashboards.
Real-World Use
Amazon uses server-side sessions to remember your shopping cart, browsing history, and recommendations across requests. When you add an item to your cart and navigate to a different page, the session cookie tells the server who you are, and the server retrieves your cart data from its session store.
Session Flow in MPAs
ββββββββββββ ββββββββββββ βββββββββββββ
β Browser β β Server β β Session β
β β β β β Store β
ββββββ¬ββββββ ββββββ¬ββββββ βββββββ¬ββββββ
β β β
β POST /login β β
β (email, password)β β
βββββββββββββββββββ>β β
β β Verify credentialsβ
β β β
β β Create session β
β ββββββββββββββββββββ>β
β β Session ID: abc123β
β β<ββββββββββββββββββββ
β β β
β Set-Cookie: β β
β session=abc123 β β
β<βββββββββββββββββββ β
β β β
β GET /dashboard β β
β Cookie: session= β β
β abc123 β β
βββββββββββββββββββ>β β
β β Lookup session β
β ββββββββββββββββββββ>β
β β User ID: 42 β
β β<ββββββββββββββββββββ
β β β
β Dashboard HTML β β
β with user data β β
β<βββββββββββββββββββ β
ββββββββββββββββββββ ββββββββββββ
Think of sessions like a locker room at a gym. When you arrive (log in), you get a key (session cookie) with a number on it. The server has a matching locker (session store) with your belongings (user data, cart items). Every time you return to the counter (make a request), you show your key, and the server retrieves your locker contents.
Session Implementation
const express = require('express');
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const redis = require('redis');
const redisClient = redis.createClient({
url: process.env.REDIS_URL || 'redis://localhost:6379'
});
redisClient.connect().catch(console.error);
const app = express();
// Session configuration
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET || 'your-secret-key',
name: 'myapp.sid', // Custom cookie name (not default 'connect.sid')
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true, // Not accessible via JavaScript
secure: true, // HTTPS only
sameSite: 'strict', // CSRF protection
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
// Login route
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await db.users.authenticate(email, password);
if (!user) {
return res.status(401).render('login', {
error: 'Invalid email or password'
});
}
// Store user data in session
req.session.userId = user.id;
req.session.userRole = user.role;
req.session.createdAt = Date.now();
// Regenerate session ID to prevent session fixation
req.session.regenerate((err) => {
if (err) return next(err);
res.redirect('/dashboard');
});
});
// Middleware to check authentication
function requireAuth(req, res, next) {
if (!req.session || !req.session.userId) {
req.session.returnTo = req.originalUrl;
return res.redirect('/login');
}
next();
}
// Protected route
app.get('/dashboard', requireAuth, async (req, res) => {
const user = await db.users.findById(req.session.userId);
res.render('dashboard', {
title: 'Dashboard',
user: user
});
});
// Logout
app.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) return next(err);
res.clearCookie('myapp.sid');
res.redirect('/');
});
});
Redis Session Store
// Redis session operations
const sessionUtils = {
// Get active session count
async getActiveSessionCount() {
const keys = await redisClient.keys('sess:*');
return keys.length;
},
// Get all sessions for a user
async getUserSessions(userId) {
const keys = await redisClient.keys('sess:*');
const sessions = [];
for (const key of keys) {
const data = await redisClient.get(key);
if (data) {
const session = JSON.parse(data);
if (session.userId === userId) {
sessions.push({
id: key.replace('sess:', ''),
createdAt: session.createdAt,
userAgent: session.userAgent
});
}
}
}
return sessions;
},
// Force logout all sessions for a user (password change)
async invalidateUserSessions(userId) {
const keys = await redisClient.keys('sess:*');
for (const key of keys) {
const data = await redisClient.get(key);
if (data) {
const session = JSON.parse(data);
if (session.userId === userId) {
await redisClient.del(key);
}
}
}
},
// Extend session TTL on activity
async touchSession(sessionId) {
await redisClient.expire(`sess:${sessionId}`, 86400); // 24 hours
}
};
// Expected output:
// Active sessions: 1,234
// User 42 has 3 active sessions
// Invalidated 3 sessions for user 42
Session Security Best Practices
// Session security configuration
const sessionSecurity = {
// Regenerate session ID after login
regenerateOnLogin: true,
// Rotate session ID periodically
rotateInterval: 15 * 60 * 1000, // 15 minutes
// Maximum sessions per user
maxConcurrentSessions: 5,
// Absolute session timeout (force re-login)
absoluteTimeout: 7 * 24 * 60 * 60 * 1000, // 7 days
// Idle session timeout
idleTimeout: 30 * 60 * 1000, // 30 minutes
// Check for session hijacking
checkUserAgent: true,
checkIP: false // IP changes are common on mobile
};
// Middleware for session rotation
app.use((req, res, next) => {
if (req.session && req.session.userId) {
// Check idle timeout
const idle = Date.now() - req.session.lastActivity;
if (idle > sessionSecurity.idleTimeout) {
req.session.destroy();
return res.redirect('/login?expired=1');
}
// Check absolute timeout
const elapsed = Date.now() - req.session.createdAt;
if (elapsed > sessionSecurity.absoluteTimeout) {
req.session.destroy();
return res.redirect('/login?expired=1');
}
// Rotate session ID periodically
const rotationElapsed = Date.now() - req.session.lastRotation;
if (rotationElapsed > sessionSecurity.rotateInterval) {
req.session.lastRotation = Date.now();
req.session.regenerate((err) => {
if (err) return next(err);
next();
});
return;
}
// Update last activity
req.session.lastActivity = Date.now();
}
next();
});
Common Mistakes
- Storing too much data in session. Session data is loaded on every request. Store only the user ID and essential state. Fetch detailed data from the database when needed.
- Not using secure cookies in production. Sessions without Secure and HttpOnly flags are vulnerable to XSS and man-in-the-middle attacks. Always enable these flags in production.
- In-memory sessions in production. The default MemoryStore leaks memory and does not scale across multiple servers. Use Redis, Memcached, or a database for production sessions.
- Not invalidating sessions on password change. When a user changes their password, all existing sessions should be invalidated to prevent the old password from still working.
- Session fixation vulnerability. Not regenerating the session ID after login allows attackers to fixate a session ID. Always call regenerate() after authentication.
Practice Questions
- How does a server-side session work with cookies?
- What is the difference between cookie-based and server-side sessions?
- Why should you use Redis for session storage in production?
- What security measures protect sessions from hijacking?
- Why should you regenerate the session ID after login?
Challenge: Implement a complete session management system for an MPA with Express.js"Express" >}}.js and Redis. Include: login with session creation and ID regeneration, protected routes that check session, session timeout (idle and absolute), session display page showing active sessions with device info, force logout of individual sessions, and session invalidation on password change.
FAQ
Mini Project
Build a session-managed MPA with Express.js and Redis: login/logout with session regeneration, protected dashboard page showing user-specific content, session display page listing all active sessions with last activity time, ability to revoke individual sessions from the profile page, idle timeout that logs out inactive users, and absolute timeout that forces re-login after 7 days.
What's Next
You understand session management. Now learn about CSRF Protection to secure your forms against cross-site request forgery attacks.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro