Перейти к содержимому

18. Sessions и Cookies

Иллюстрация к уроку

Сессии и куки — альтернатива JWT. Удобны для веб-приложений, где сервер хранит состояние пользователя.

Окно терминала
npm install cookie-parser
const cookieParser = require('cookie-parser');
// Подключаем (нужен для чтения кук)
app.use(cookieParser(process.env.COOKIE_SECRET)); // секрет для подписанных кук
// Установка куки
app.get('/set-cookie', (req, res) => {
// Простая кука
res.cookie('language', 'ru');
// С опциями
res.cookie('theme', 'dark', {
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 дней в мс
httpOnly: false, // доступна из JS (нужна для темы)
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
});
// Подписанная кука (защита от подделки)
res.cookie('userId', '42', {
signed: true, // подписывается с cookie.secret
httpOnly: true,
secure: true,
maxAge: 7 * 24 * 60 * 60 * 1000,
});
res.json({ message: 'Куки установлены!' });
});
// Чтение куки
app.get('/read-cookie', (req, res) => {
console.log(req.cookies); // { language: 'ru', theme: 'dark' }
console.log(req.signedCookies); // { userId: '42' }
const theme = req.cookies.theme;
const userId = req.signedCookies.userId; // автоматически проверяет подпись
res.json({ theme, userId });
});
// Удаление куки
app.get('/clear', (req, res) => {
res.clearCookie('theme');
res.clearCookie('userId');
res.json({ message: 'Куки удалены' });
});
// Лучшие практики для auth куки
res.cookie('session', sessionId, {
httpOnly: true, // ❗ ВСЕГДА для auth кук — недоступна из JavaScript
secure: true, // ❗ ВСЕГДА в production — только HTTPS
sameSite: 'strict', // Защита от CSRF (strict/lax/none)
// sameSite: 'strict' — кука не отправляется при переходе с другого сайта
// sameSite: 'lax' — кука не отправляется для POST с другого сайта
// sameSite: 'none' — отправляется всегда (нужен secure: true)
maxAge: 24 * 60 * 60 * 1000, // 1 день (вместо expires)
path: '/',
domain: process.env.COOKIE_DOMAIN || undefined,
});
Окно терминала
npm install express-session connect-redis ioredis
const session = require('express-session');
const Redis = require('ioredis');
const RedisStore = require('connect-redis').default;
// Подключаем Redis для хранения сессий
const redisClient = new Redis(process.env.REDIS_URL);
const store = new RedisStore({ client: redisClient });
app.use(session({
store,
secret: process.env.SESSION_SECRET, // для подписи session ID
resave: false, // не пересохранять если не изменилась
saveUninitialized: false, // не создавать пустые сессии
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 24 * 60 * 60 * 1000, // 1 день
},
// Имя куки (по умолчанию 'connect.sid')
name: 'sid',
}));
// Авторизация через сессию
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await db.user.findUnique({ where: { email } });
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
return res.status(401).json({ error: 'Неверный email или пароль' });
}
// Сохраняем данные в сессии
req.session.userId = user.id;
req.session.role = user.role;
req.session.loginAt = Date.now();
// Принудительно сохраняем (обычно автоматически)
req.session.save((err) => {
if (err) return next(err);
res.json({ message: 'Вход выполнен' });
});
});
// Middleware проверки сессии
function sessionAuth(req, res, next) {
if (!req.session.userId) {
return res.status(401).json({ error: 'Не авторизован' });
}
next();
}
// Защищённый роут
app.get('/profile', sessionAuth, async (req, res) => {
const user = await db.user.findUnique({
where: { id: req.session.userId }
});
res.json({ data: user });
});
// Выход
app.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) return res.status(500).json({ error: 'Ошибка выхода' });
res.clearCookie('sid');
res.json({ message: 'Вышли из системы' });
});
});
// Информация о сессии
app.get('/session-info', sessionAuth, (req, res) => {
res.json({
userId: req.session.userId,
role: req.session.role,
loginAt: req.session.loginAt,
sessionId: req.session.id, // осторожно — не отправляй в реальном API!
});
});
Окно терминала
npm install connect-flash
const flash = require('connect-flash');
app.use(flash());
// Установить flash сообщение
app.post('/login', async (req, res) => {
// ...логика входа...
if (success) {
req.flash('success', 'Добро пожаловать!');
res.redirect('/dashboard');
} else {
req.flash('error', 'Неверные данные');
res.redirect('/login');
}
});
// Читать flash сообщения
app.get('/dashboard', (req, res) => {
const messages = req.flash();
res.render('dashboard', {
success: messages.success?.[0],
error: messages.error?.[0],
});
});
JWT Sessions
─────────────────────────────── ──────────────────────────────
Stateless (нет хранилища) Stateful (Redis, БД)
Масштабируется легко Нужен shared storage
Нельзя отозвать (до истечения) Можно отозвать немедленно
Подходит для API, мобильных Подходит для веб-приложений
Токен в localStorage/header Session ID в httpOnly cookie
Уязвим если украдут токен Более безопасный

Рекомендация:

  • API для мобильных → JWT
  • Веб-приложение с сервером → Sessions
  • Нужна отзываемость → Sessions или JWT + blacklist в Redis
  1. Настрой cookie-parser и создай роут, устанавливающий несколько кук с разными настройками
  2. Настрой express-session с Redis хранилищем
  3. Реализуй вход/выход через сессию
  4. Создай middleware sessionAuth для защиты роутов
  5. Сравни: реализуй одно и то же через JWT и через сессии — почувствуй разницу