Сессии: серверная авторизация

Серверные сессии — механизм авторизации, при котором сервер хранит состояние пользователя (сессию), а клиент идентифицирует себя через session ID в куке.

Зачем нужно

Сессии — классический подход к авторизации до эпохи JWT. Сервер полностью контролирует жизненный цикл сессии: может немедленно инвалидировать её (logout, блокировка). JWT этого не умеет без дополнительной инфраструктуры. Понимание сессий необходимо для работы с Express.js, Django, Rails.

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

  • Традиционные веб-приложения с серверным рендерингом (MPA)
  • Admin-панели с возможностью принудительного logout
  • Приложения с высокими требованиями безопасности (банки)
  • Express.js с express-session

Принцип работы

1. Пользователь отправляет login + password
2. Сервер проверяет credentials
3. Сервер создаёт сессию: { sessionId: "abc123", userId: 42, createdAt: ... }
4. Сессия сохраняется в хранилище (Redis, БД, память)
5. Сервер возвращает куку: Set-Cookie: sessionId=abc123; HttpOnly; Secure
6. При каждом запросе браузер автоматически отправляет куку
7. Сервер ищет сессию по sessionId и знает, кто делает запрос

Реализация в Express

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

const app = express;
const redisClient = createClient({ url: process.env.REDIS_URL });
redisClient.connect;

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET, // длинная случайная строка
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,    // недоступно через JS
    secure: true,      // только HTTPS
    sameSite: 'lax',   // защита от CSRF
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7 дней
  },
}));

// Логин — создаём сессию
app.post('/auth/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await db.users.findByEmail(email);

  if (!user || !await bcrypt.compare(password, user.passwordHash)) {
    return res.status(401).json({ error: 'Неверные данные' });
  }

  // Регенерация ID сессии — защита от session fixation
  req.session.regenerate(err => {
    if (err) return res.status(500).json({ error: 'Session error' });
    req.session.userId = user.id;
    req.session.role = user.role;
    res.json({ ok: true });
  });
});

// Middleware авторизации
function requireAuth(req, res, next) {
  if (!req.session.userId) {
    return res.status(401).json({ error: 'Не авторизован' });
  }
  next;
}

// Защищённый маршрут
app.get('/api/profile', requireAuth, async (req, res) => {
  const user = await db.users.find(req.session.userId);
  res.json(user);
});

// Logout — уничтожаем сессию
app.post('/auth/logout', (req, res) => {
  req.session.destroy(err => {
    res.clearCookie('connect.sid');
    res.json({ ok: true });
  });
});

Сессии vs JWT

Критерий Сессии JWT
Хранение Сервер (Redis/DB) Клиент (localStorage/cookie)
Инвалидация Немедленная Ждать истечения токена
Масштабирование Нужен Redis Без хранилища
Размер Малый (только ID) Больше (весь payload)
Состояние Stateful Stateless

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

  • Хранение сессий в памяти процесса (MemoryStore) — при перезапуске теряются все сессии
  • Не регенерируют session ID после логина — уязвимость session fixation
  • Cookie без HttpOnly — доступно через document.cookie (XSS-уязвимость)
  • Cookie без Secure — передаётся по HTTP (перехват)
  • Не очищают сессию при logout — сессия остаётся валидной в хранилище

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

Ресурсы