iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Session Store

Storing user sessions in Redis gives you sub-millisecond access, automatic expiry, shared state across replicas, and easy invalidation — without the SQL overhead of a database table. The trade-offs are durability and the need to design for both eviction and security.

Server, expiry, security, scaling

EXAMPLE
// 1) Why Redis for sessions
// • Fast reads/writes (<1 ms)
// • Easy TTL = expiry
// • Shared across web replicas (no sticky sessions required)
// • Cheap to evict / invalidate / rotate
//
// Use a SEPARATE Redis cluster from your cache when sessions matter for compliance:
//   • Eviction policies differ (sessions: noeviction or volatile-ttl; cache: allkeys-lru)
//   • Backup + persistence requirements differ

// 2) Express + connect-redis (Node)
// npm install express express-session connect-redis redis
import session from 'express-session';
import { RedisStore } from 'connect-redis';
import { createClient } from 'redis';
import crypto from 'node:crypto';

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

const app = express();
app.use(session({
    store: new RedisStore({ client: redis, prefix: 'sess:' }),
    secret: process.env.SESSION_SECRET,                  // sign the session cookie
    name:    'sid',
    resave:  false,
    saveUninitialized: false,
    cookie: {
        httpOnly: true,
        secure:   true,
        sameSite: 'lax',
        maxAge:   1000 * 60 * 60 * 24 * 7,                // 7 days
        path:     '/',
    },
    rolling: true,                                          // refresh expiry on every request
}));

// 3) Save data
app.post('/login', async (req, res) => {
    const user = await verifyCredentials(req.body.email, req.body.password);
    if (!user) return res.sendStatus(401);
    // ⚠ regenerate to prevent session fixation
    req.session.regenerate((err) => {
        if (err) return res.sendStatus(500);
        req.session.userId = user.id;
        req.session.role   = user.role;
        res.json({ ok: true });
    });
});

app.get('/me', (req, res) => {
    if (!req.session.userId) return res.sendStatus(401);
    res.json({ userId: req.session.userId, role: req.session.role });
});

// Logout
app.post('/logout', (req, res) => {
    req.session.destroy(() => res.clearCookie('sid').sendStatus(204));
});

// 4) The key in Redis
//   sess:<sessionId>    →    JSON blob of req.session data
//   TTL                  =    cookie maxAge (auto-extends with 'rolling: true')

// 5) Stateless JWT vs session — pick wisely
// JWT          — self-contained; no server lookup; revocation HARDER
// Session in Redis — server lookup per request; trivial revocation; cheap to rotate; private payload
// For browser apps + sensitive data: prefer Redis sessions.
// For machine-to-machine: JWTs work well + a short TTL.

// 6) Session security checklist
// • httpOnly cookie — XSS can't read sid
// • secure (HTTPS only)
// • sameSite = 'lax' or 'strict' — CSRF prevention
// • signed by a high-entropy secret rotated periodically
// • regenerate sid on login + privilege change (session fixation defense)
// • destroy session on logout (don't just clear cookie)
// • idle TTL + absolute max lifetime (force re-auth after N hours)
// • per-user concurrent session limits (optional but recommended for sensitive apps)

// 7) Per-user session list
async function trackSession(userId, sid) {
    await redis.sAdd(`user:${userId}:sessions`, sid);
    await redis.expire(`user:${userId}:sessions`, 86400 * 30);
}
async function revokeAllSessionsFor(userId) {
    const sids = await redis.sMembers(`user:${userId}:sessions`);
    if (sids.length) {
        await redis.del(sids.map((s) => `sess:${s}`));
        await redis.del(`user:${userId}:sessions`);
    }
}
// Useful for 'log out of all devices' UX.

// 8) Rolling vs absolute expiry
// rolling: true  — extend each request; user stays logged in while active
// absolute       — fixed expiry from login; forces re-auth after a strict window
// Hybrid: short idle TTL + long absolute via two timestamps in the session

// 9) Avoiding the dogpile
// On expiry, multiple in-flight requests fail. Either:
//   • Refresh slightly BEFORE expiry (90% of TTL)
//   • Use a refresh token alongside
//   • Send a 401 + ask client to refresh + retry

// 10) Connection + cluster considerations
// • Use Redis Cluster or Sentinel for HA
// • Failover: client library reconnects + re-reads session
// • Pipelining: connect-redis groups operations
// • Per-region replication for multi-region apps (read replicas in each region)

// 11) Persistence + memory policy
// • appendonly yes      — AOF for durability
// • maxmemory-policy noeviction OR volatile-ttl
//   - noeviction: refuses writes when memory full — sessions can't be lost
//   - volatile-ttl: evicts keys nearest expiry — graceful degradation
// • Memory budget: estimate avg session size × max concurrent users
// • Compress big session blobs (zlib) if storing tokens / preferences in session

// 12) Monitoring + alerting
// • redis memory usage
// • session creation / destruction rate
// • TTL distribution (P50, P95)
// • count of sessions per user (anomaly = compromised account?)
// • failed authentication rate paired with session lookup misses

// 13) Other frameworks
// Django:    django-redis-sessions; settings.SESSION_ENGINE = 'redis_sessions.session'
// Flask:     Flask-Session with type='redis'
// Rails:     redis-store gem + session_store :redis_store
// Laravel:   SESSION_DRIVER=redis in .env
// Go:        github.com/gorilla/sessions + redistore.NewRediStore(...)

// 14) Common bugs
// • secret too short or static across environments — rotate per env, store in vault
// • saveUninitialized: true → every visitor gets a session row; explosion of empty sessions
// • No regenerate on login → session fixation
// • Cookie httpOnly=false → XSS can steal sid
// • SameSite=None without secure → browsers reject
// • TTL longer than required by compliance — sessions outlive consent
// • Storing huge data in session (entire user profile) — keep it tiny; lookup richer data from DB
// • Eviction with allkeys-lru on the session store → random user logouts under memory pressure
// • One Redis for cache AND sessions on same instance — cache evictions silently log users out

Why it matters

Redis sessions give you sub-ms reads, automatic expiry, and shared state across app replicas. Pair connect-redis with HttpOnly + Secure + SameSite cookies, regenerate sids on login to defeat fixation, and run sessions on a separate Redis (with noeviction or volatile-ttl) so a busy cache can’t silently log everyone out.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
SETEX session:abc 1800 $json     # 30-min sessions
GET session:abc
DEL session:abc
Try it Yourself »

Discussion

Loading…