Зачем нужно
Сессионная аутентификация — классический подход, при котором сервер хранит информацию о пользователе в сессии, а браузер отправляет идентификатор сессии через cookie. В отличие от JWT (stateless), сессии — stateful: состояние хранится на сервере.
Где используется
- Традиционные веб-приложения (SSR: Rails, Django, PHP)
- Приложения с высокими требованиями к безопасности (банки)
- Когда нужно мгновенно разлогинить пользователя (отозвать сессию)
- Корпоративные приложения
Как работают Cookies
1. Сервер → устанавливает cookie:
HTTP/1.1 200 OK
Set-Cookie: session_id=abc123; HttpOnly; Secure; SameSite=Strict
2. Браузер → автоматически отправляет cookie с каждым запросом:
GET /api/profile
Cookie: session_id=abc123
3. Сервер → находит сессию по ID, отдаёт данные пользователя
res.cookie('session_id', 'abc123', {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000,
path: '/',
domain: '.example.com',
});
res.clearCookie('session_id');
Атрибуты Cookie
Set-Cookie: name=value; HttpOnly; Secure; SameSite=Strict; Max-Age=86400; Path=/; Domain=.example.com
| Атрибут |
Значение |
Назначение |
HttpOnly |
— |
JS не видит cookie (document.cookie) — защита от XSS |
Secure |
— |
Только через HTTPS |
SameSite=Strict |
— |
Cookie не отправляется при переходе с другого сайта |
SameSite=Lax |
— |
Отправляется при навигации (GET), не при POST |
SameSite=None |
— |
Отправляется всегда (нужен Secure) |
Max-Age |
секунды |
Время жизни (0 = удалить) |
Expires |
дата |
Время истечения |
Path |
путь |
Для каких путей действует |
Domain |
домен |
Для каких доменов действует |
Сессионная аутентификация
const express = require('express');
const session = require('express-session');
const bcrypt = require('bcrypt');
const app = express;
app.use(express.json());
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 24 * 60 * 60 * 1000,
},
}));
app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
const user = await db.users.findByEmail(email);
if (!user) return res.status(401).json({ error: 'Неверные данные' });
const valid = await bcrypt.compare(password, user.passwordHash);
if (!valid) return res.status(401).json({ error: 'Неверные данные' });
req.session.userId = user.id;
req.session.role = user.role;
res.json({ message: 'Авторизован', user: { id: user.id, name: user.name } });
});
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.findById(req.session.userId);
res.json(user);
});
app.post('/api/logout', (req, res) => {
req.session.destroy((err) => {
if (err) return res.status(500).json({ error: 'Ошибка' });
res.clearCookie('connect.sid');
res.json({ message: 'Вы вышли' });
});
});
Хранение сессий
app.use(session({ }));
const RedisStore = require('connect-redis').default;
const { createClient } = require('redis');
const redisClient = createClient;
app.use(session({
store: new RedisStore({ client: redisClient }),
}));
const MongoStore = require('connect-mongo');
app.use(session({
store: MongoStore.create({
mongoUrl: 'mongodb://localhost:27017/sessions',
}),
}));
Cookie на клиенте (JavaScript)
console.log(document.cookie);
document.cookie = 'theme=dark; path=/; max-age=86400';
document.cookie = 'lang=ru; path=/';
document.cookie = 'theme=; max-age=0';
function getCookie(name) {
const match = document.cookie.match(
new RegExp('(?:^|; )' + name + '=([^;]*)')
);
return match ? decodeURIComponent(match[1]) : null;
}
function setCookie(name, value, days = 7) {
const maxAge = days * 24 * 60 * 60;
document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${maxAge}`;
}
function deleteCookie(name) {
document.cookie = `${name}=; max-age=0; path=/`;
}
Сессии vs JWT
| Аспект |
Сессии |
JWT |
| Хранение |
На сервере |
На клиенте |
| Stateful/Stateless |
Stateful |
Stateless |
| Отзыв |
Мгновенный (удалить сессию) |
Сложный (blacklist) |
| Масштабирование |
Нужен общий store (Redis) |
Нет состояния на сервере |
| Размер |
Cookie ~50 байт (ID) |
JWT ~500+ байт |
| Микросервисы |
Нужен общий store |
Каждый сервис верифицирует сам |
| XSS |
httpOnly защищает |
В localStorage — уязвим |
Частые ошибки
- Нет
HttpOnly — XSS-атака крадёт session cookie через document.cookie
- Нет
Secure — cookie передаётся по HTTP, перехват через MITM
SameSite=None без причины — cookie отправляется со всех сайтов (CSRF)
- Сессии в памяти — при рестарте сервера все пользователи разлогиниваются
- Нет CSRF-защиты — при SameSite=None или Lax нужен CSRF-токен
- Огромные cookies — лимит 4KB на cookie, данные хранить в сессии на сервере
Практика
- Настроить express-session с cookie-конфигурацией
- Реализовать login/logout с сессиями
- Добавить requireAuth middleware
- Подключить Redis как session store
- Проверить в DevTools → Application → Cookies: HttpOnly, Secure, SameSite
Связанные темы
- JWT — альтернативный stateless подход
- OAuth 2.0 — OAuth часто завершается созданием сессии
- HTTP протокол — Set-Cookie заголовок
- CORS — credentials и кросс-доменные cookies
Ресурсы