Cookie: безопасные атрибуты (HttpOnly, Secure, SameSite)

Атрибуты куки HttpOnly, Secure и SameSite — механизмы защиты от XSS, перехвата трафика и CSRF-атак.

Зачем нужно

Кука с session ID или токеном — высокоценная цель для атакующего. Без защитных атрибутов XSS может украсть куку, незашифрованный HTTP — открыть её перехватчику, а CSRF — использовать куку для атаки от имени пользователя. Три атрибута вместе закрывают эти векторы атак.

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

  • Хранение session ID на клиенте
  • Refresh token в куке (безопаснее, чем в localStorage)
  • Любые куки с чувствительными данными
  • Express.js, Django, Rails — при настройке cookies

HttpOnly

HttpOnly — кука недоступна через JavaScript (document.cookie)
Защищает от: XSS — скрипт не может прочитать куку
# Сервер устанавливает куку
Set-Cookie: sessionId=abc123; HttpOnly

# JavaScript НЕ МОЖЕТ прочитать
console.log(document.cookie); // "" — пусто
// Express
res.cookie('sessionId', sessionId, { httpOnly: true });

Secure

Secure — кука передаётся только по HTTPS
Защищает от: перехвата трафика (man-in-the-middle)
Set-Cookie: sessionId=abc123; Secure

# При HTTP-запросе браузер НЕ отправит эту куку
// Express — production
res.cookie('sessionId', sessionId, {
  secure: process.env.NODE_ENV === 'production', // только HTTPS в prod
});

SameSite

SameSite контролирует, когда кука отправляется при кросс-сайт запросах
Защищает от: CSRF — атака с другого сайта

Значения SameSite

SameSite=Strict — кука только при навигации с того же сайта
  Пример: кнопка «Оплатить» с другого сайта не получит куку
  Минус: ссылки из email тоже не получат куку (неудобно)

SameSite=Lax (рекомендуется) — кука при безопасных кросс-сайт навигациях (GET, ссылка)
  Пример: ссылка из email → кука отправлена (пользователь видит свой профиль)
  CSRF-запрос с POST → кука НЕ отправлена
  Это дефолт в современных браузерах

SameSite=None — всегда отправлять куку (нужно с Secure)
  Использование: сторонние куки в iframe, CORS с credentials
# Оптимальная комбинация для session cookie
Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=604800

Настройка в Express

const session = require('express-session');

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    maxAge: 7 * 24 * 60 * 60 * 1000, // 7 дней в мс
    path: '/',
  },
}));

Ручная установка куки

// Установка
res.setHeader('Set-Cookie', [
  `token=${token}; HttpOnly; Secure; SameSite=Lax; Max-Age=86400; Path=/`,
]);

// Через res.cookie в Express
res.cookie('token', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'lax',
  maxAge: 86400 * 1000,
});

// Очистка (logout)
res.clearCookie('token', { httpOnly: true, secure: true, path: '/' });

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

  • Хранение JWT в localStorage вместо HttpOnly-куки — доступно для XSS
  • Secure без HTTPS — кука никогда не передаётся (dev окружение)
  • SameSite=None без Secure — браузеры игнорируют такую куку
  • Не указывают Path=/ — кука отправляется только с текущего пути

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

Ресурсы