Sessions vs Cookies
| Feature | Cookies | Sessions |
|---|
| Where data lives | Client (browser) | Server (memory/DB/Redis) |
| What’s stored | Session ID only | User data, cart, preferences |
| Size limit | ~4KB per cookie | Limited by storage |
| Security | Tamperable (must sign) | Controlled by server |
| Persistence | Client-controlled | Server-controlled |
Browser Server
│ │
│── POST /login ────────────────→│
│ { username, password } │ Create session
│ │ Store { userId, role } in session store
│ │
│←── Set-Cookie: connect.sid=...─│ Send session ID in signed cookie
│ │
│── GET /dashboard ─────────────→│
│ Cookie: connect.sid=... │ Look up session by ID
│ │ Load { userId, role } from session store
│←── 200 Dashboard HTML ─────────│
Installing express-session
npm install express-session
Basic Setup
const express = require('express');
const session = require('express-session');
const app = express();
app.use(session({
secret: process.env.SESSION_SECRET || 'your-secret-key',
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000,
sameSite: 'lax'
}
}));
app.listen(3000);
Session Configuration Options
app.use(session({
secret: process.env.SESSION_SECRET || 'fallback-secret',
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
httpOnly: true,
maxAge: 86400000,
sameSite: 'strict',
path: '/',
domain: '.example.com'
},
store: new RedisStore({ client: redisClient }),
name: 'myapp.sid',
rolling: false,
genid: (req) => crypto.randomUUID()
}));
Session Usage in Routes
app.post('/login', (req, res) => {
const { username, password } = req.body;
if (username === 'admin' && password === 'secret') {
req.session.userId = 1;
req.session.username = username;
req.session.role = 'admin';
req.session.loggedInAt = new Date().toISOString();
res.json({ message: 'Logged in successfully' });
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
});
app.get('/dashboard', (req, res) => {
if (!req.session.userId) {
return res.status(401).json({ error: 'Please login first' });
}
res.json({
message: `Welcome, ${req.session.username}!`,
role: req.session.role,
loggedInAt: req.session.loggedInAt
});
});
app.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: 'Logout failed' });
}
res.clearCookie('connect.sid');
res.json({ message: 'Logged out' });
});
});
Session Middleware for Auth
function requireAuth(req, res, next) {
if (!req.session.userId) {
return res.status(401).json({ error: 'Authentication required' });
}
next();
}
function requireRole(...roles) {
return (req, res, next) => {
if (!req.session.userId) {
return res.status(401).json({ error: 'Authentication required' });
}
if (!roles.includes(req.session.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
app.get('/profile', requireAuth, (req, res) => {
res.json({ user: req.session.username });
});
app.get('/admin', requireRole('admin'), (req, res) => {
res.json({ secret: 'Admin data' });
});
Session Store - MemoryStore (Default)
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: false
}));
Session Store - Redis (Production)
npm install redis connect-redis
const RedisStore = require('connect-redis').default;
const { createClient } = require('redis');
const redisClient = createClient({
url: process.env.REDIS_URL || 'redis://localhost:6379'
});
redisClient.connect().catch(console.error);
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { secure: true, httpOnly: true, maxAge: 86400000 }
}));
Other Session Stores
| Store | Use Case |
|---|
| Redis | Fast, production-ready, shared across processes |
MongoDB (connect-mongo) | Good if you already use MongoDB |
PostgreSQL (connect-pg-simple) | Good if you already use PostgreSQL |
MySQL (express-mysql-session) | Good if you already use MySQL |
Cookie Parser Utility
For reading unsigned cookies sent by the client:
npm install cookie-parser
const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.get('/cookies', (req, res) => {
console.log(req.cookies);
console.log(req.signedCookies);
res.json({ cookies: req.cookies });
});
Full Auth Flow with Sessions
const express = require('express');
const session = require('express-session');
const bcrypt = require('bcrypt');
const User = require('./models/User');
const app = express();
app.use(express.json());
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { httpOnly: true, secure: false, maxAge: 86400000 }
}));
app.post('/register', async (req, res, next) => {
try {
const { username, password } = req.body;
const hashedPassword = await bcrypt.hash(password, 10);
const user = await User.create({ username, password: hashedPassword });
res.status(201).json({ message: 'User created' });
} catch (err) { next(err); }
});
app.post('/login', async (req, res, next) => {
try {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({ error: 'Invalid credentials' });
}
req.session.userId = user._id;
req.session.username = user.username;
req.session.role = user.role;
res.json({ message: 'Login successful', user: { username: user.username, role: user.role } });
} catch (err) { next(err); }
});
app.get('/me', (req, res) => {
if (!req.session.userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
res.json({
userId: req.session.userId,
username: req.session.username,
role: req.session.role
});
});
app.post('/logout', (req, res) => {
req.session.destroy();
res.clearCookie('connect.sid');
res.json({ message: 'Logged out' });
});
app.listen(3000);
Security Considerations
cookie: {
secure: false,
httpOnly: false,
sameSite: false
}
cookie: {
secure: true,
httpOnly: true,
sameSite: 'strict',
maxAge: 86400000
}
- Rotate
secret periodically
- Use a proper session store (Redis, DB) in production
- Set
httpOnly: true to prevent XSS access
- Set
secure: true in production (HTTPS)
- Use
sameSite: 'lax' or 'strict' for CSRF protection
- Regenerate session ID on privilege escalation:
req.session.regenerate((err) => {
req.session.userId = user.id;
});
Key Takeaways
- Express sessions store data server-side, only the session ID is stored in a cookie
- Install
express-session and configure with a secret and cookie options
- Access session data via
req.session - it’s a plain object you can read/write
- Use
req.session.destroy() to log out
- For production, use Redis or another external session store
- Set
httpOnly: true, secure: true, and sameSite: 'strict' for security
- Use session middleware (
requireAuth) to protect routes