Sessions are the bridge between stateless HTTP and stateful user identity. Get them wrong, and attackers hijack accounts, escalate privileges, and impersonate users. Secure sessions require careful architecture: token generation, rotation, revocation, and validation at every step.
1. Generate Cryptographically Secure Tokens
Weak tokens are predictable. Use libraries designed for token generation.
import crypto from 'crypto';
import jwt from 'jsonwebtoken';
function generateSessionToken() {
// Cryptographically secure random token
const randomToken = crypto.randomBytes(32).toString('hex');
// JWT with short expiration
const jwtToken = jwt.sign(
{ token: randomToken, type: 'session' },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
return jwtToken;
}
2. Use Refresh Token Rotation
Access tokens are short-lived. Refresh tokens should rotate with every use, invalidating the old one.
app.post('/refresh', (req, res) => {
const { refreshToken } = req.body;
// Verify and decode
const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET);
// Check if token is in the revocation list
if (isRevoked(refreshToken)) {
return res.status(403).json({ error: 'Refresh token revoked' });
}
// Issue new tokens
const newAccessToken = jwt.sign({ userId: decoded.userId }, process.env.JWT_SECRET, { expiresIn: '15m' });
const newRefreshToken = jwt.sign({ userId: decoded.userId }, process.env.REFRESH_SECRET, { expiresIn: '7d' });
// Revoke old refresh token
revokeToken(refreshToken);
res.json({ accessToken: newAccessToken, refreshToken: newRefreshToken });
});
3. Store Sessions Securely
If using server-side sessions, store them encrypted and indexed for fast lookup.
import Redis from 'ioredis';
import crypto from 'crypto';
const redis = new Redis({ password: process.env.REDIS_PASSWORD });
async function storeSession(userId, metadata) {
const sessionId = crypto.randomUUID();
const sessionData = JSON.stringify({
userId,
createdAt: Date.now(),
...metadata
});
// Store with TTL (1 hour)
await redis.setex(`session:${sessionId}`, 3600, sessionData);
return sessionId;
}
async function validateSession(sessionId) {
const data = await redis.get(`session:${sessionId}`);
if (!data) return null;
return JSON.parse(data);
}
4. Implement Secure HttpOnly Cookies
Store tokens in httpOnly, secure cookies that can't be accessed by JavaScript. Combine with CSRF protection.
app.post('/login', (req, res) => {
// Authenticate user...
const token = generateSessionToken();
res.cookie('sessionToken', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'Strict',
maxAge: 1000 * 60 * 60 // 1 hour
});
// Include CSRF token in response body
const csrfToken = crypto.randomBytes(32).toString('hex');
res.json({ csrfToken });
});
5. Detect and Respond to Session Anomalies
Log session events and alert on suspicious patterns: multiple IPs, unusual locations, or rapid token refresh.
async function validateSessionIntegrity(sessionId, req) {
const session = await redis.get(`session:${sessionId}`);
const current = {
ip: req.ip,
userAgent: req.headers['user-agent']
};
// Check for suspicious changes
if (session.ip !== current.ip) {
logger.warn(`Session hijack attempt: ${sessionId}`, { session, current });
await redis.del(`session:${sessionId}`);
return false;
}
return true;
}
Thanks for reading! If this post was insightful for you, please share it with your team or leave a comment with your own security wins.
Sessions are the keys to your users' accounts. Protect them like you'd protect your own.
With token rotation, secure storage, and anomaly detection, session management becomes a fortress rather than a liability.
Build session security with us: kodex.studio
Top comments (0)