Session Management: лучшие практики

Session Management — набор практик для безопасного создания, поддержания и завершения пользовательских сессий в веб-приложениях.

Зачем нужно

Слабое управление сессиями — одна из главных причин взломов (OWASP Top 10). Предсказуемые session ID, отсутствие инвалидации при logout, слишком длинный срок жизни — каждая из этих уязвимостей позволяет атакующему получить доступ к чужому аккаунту. Правильный session management снижает эту поверхность атаки.

Где используется

  • Все приложения с серверными сессиями (Express, Django, Rails)
  • Admin-панели с принудительным logout
  • Банковские и финансовые приложения
  • Приложения с управлением активными сессиями

Лучшие практики

1. Случайные и длинные session ID

// session ID должен быть непредсказуемым
const crypto = require('crypto');
const sessionId = crypto.randomBytes(32).toString('hex'); // 256-bit

// express-session делает это автоматически
// НЕ делайте так:
const badId = `session_${Date.now()}_${userId}`; // предсказуемо!

2. Регенерация ID после логина (session fixation)

app.post('/login', async (req, res) => {
  // Проверяем credentials...
  const user = await authenticate(req.body.email, req.body.password);
  if (!user) return res.status(401).end();

  // ОБЯЗАТЕЛЬНО регенерировать ID после логина
  req.session.regenerate((err) => {
    req.session.userId = user.id;
    req.session.loginAt = Date.now();
    req.session.ip = req.ip; // для последующей проверки
    res.json({ ok: true });
  });
});

3. Полное уничтожение сессии при logout

app.post('/logout', (req, res) => {
  req.session.destroy(err => {
    if (err) console.error(err);
    // Удаляем куку на клиенте
    res.clearCookie('connect.sid', { path: '/' });
    res.json({ ok: true });
  });
});

4. Ограничение срока жизни сессии

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    maxAge: 2 * 60 * 60 * 1000, // 2 часа активности
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
  },
}));

// Absolute timeout — независимо от активности
function absoluteTimeout(req, res, next) {
  const LOGIN_TIME = req.session.loginAt;
  const MAX_SESSION = 8 * 60 * 60 * 1000; // 8 часов максимум

  if (LOGIN_TIME && Date.now() - LOGIN_TIME > MAX_SESSION) {
    return req.session.destroy(() => {
      res.status(401).json({ error: 'Session expired' });
    });
  }
  next;
}

5. Хранение в Redis (не в памяти)

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

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

app.use(session({
  store: new RedisStore({ client, ttl: 7200 }), // TTL 2 часа
  secret: process.env.SESSION_SECRET,
  // ...
}));

6. Привязка сессии к IP / User-Agent

function validateSession(req, res, next) {
  if (!req.session.userId) return res.status(401).end();

  // Проверяем, что сессия не украдена
  if (req.session.ip && req.session.ip !== req.ip) {
    req.session.destroy(() => {});
    return res.status(401).json({ error: 'Session hijacking detected' });
  }
  next;
}

Чеклист безопасной сессии

✅ session ID > 128 бит случайных данных
✅ regenerate после успешного логина
✅ destroy + clearCookie при logout
✅ HttpOnly + Secure + SameSite=Lax на куке
✅ Хранение в Redis, не в памяти процесса
✅ Absolute timeout (8 часов) + idle timeout (30 мин)
✅ HTTPS обязателен

Частые ошибки

  • MemoryStore в продакшне — при рестарте все сессии теряются
  • Нет regenerate при логине — session fixation уязвимость
  • Logout только на клиенте (удаление куки без destroy на сервере) — сессия живёт
  • Слишком долгий срок сессии (месяцы) для чувствительных приложений

Связанные темы

Ресурсы