`Cookie` в TypeScript

`Cookie` в TypeScript

В этой статье объясняется работа с Cookie в TypeScript.

Мы рассмотрим практические шаблоны безопасной и надежной работы с файлами cookie как в браузере, так и на сервере.

YouTube Video

Cookie в TypeScript

Базовые концепции файлов cookie

Файл cookie — это механизм хранения на клиенте небольших строк (пар «имя=значение»), создаваемых через HTTP-заголовок Set-Cookie или через document.cookie. Их поведением можно управлять с помощью атрибутов безопасности (HttpOnly, Secure, SameSite и т. д.).

Базовые операции в браузере: document.cookie

Ниже приведен минимальный пример записи файла cookie в браузере. Создайте файл cookie, добавив строку к document.cookie.

 1// Set a simple cookie that expires in 7 days.
 2// Note: Comments are in English per the user's preference.
 3const setSimpleCookie = (name: string, value: string) => {
 4  const days = 7;
 5  const expires = new Date(Date.now() + days * 86400_000).toUTCString();
 6  // name=value; Expires=...; Path=/
 7  document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; Expires=${expires}; Path=/`;
 8};
 9
10setSimpleCookie('theme', 'dark');
  • Этот код создает файл cookie со сроком жизни 7 дней. Его легко использовать на стороне браузера, но поскольку нельзя задать HttpOnly, следует избегать хранения конфиденциальной информации.

Чтение файлов cookie (браузер)

Следующая функция — вспомогательная; она извлекает значение cookie с указанным именем из document.cookie. document.cookie возвращает строку, в которой записи разделены точкой с запятой и пробелом ('; '). Поэтому мы разбиваем её и ищем нужный cookie.

 1// Parse document.cookie and return value for given name or null if not found.
 2const getCookie = (name: string): string | null => {
 3  const cookies = document.cookie ? document.cookie.split('; ') : [];
 4  for (const cookie of cookies) {
 5    const [k, ...rest] = cookie.split('=');
 6    const v = rest.join('=');
 7    if (decodeURIComponent(k) === name) {
 8      return decodeURIComponent(v);
 9    }
10  }
11  return null;
12};
13
14// Example usage:
15const theme = getCookie('theme'); // => "dark" if set
16console.log('theme cookie:', theme);
  • Эта функция декодирует и имя, и значение cookie, чтобы безопасно сравнить и извлечь их. Она обрабатывает дубликаты имён cookie и закодированные специальные символы, что делает её простой, но надёжной реализацией.

Понимание и применение атрибутов безопасности

У файлов cookie есть несколько важных атрибутов; каждый из них определяет безопасность и область их действия.

  • Атрибут HttpOnly предотвращает доступ к файлу cookie из JavaScript, что помогает смягчить XSS-атаки (межсайтовый скриптинг). Учтите, что установить HttpOnly со стороны браузера невозможно.
  • Атрибут Secure ограничивает отправку файлов cookie только по HTTPS, снижая риск перехвата и подмены.
  • Атрибут SameSite управляет отправкой файлов cookie при кросс-сайтовых запросах и помогает предотвращать атаки CSRF (межсайтовая подделка запросов).
  • Атрибут Path задаёт область путей запросов, для которых отправляется файл cookie, а атрибут Domain указывает домен, в пределах которого файл cookie действителен.
  • Установив атрибут Expires или Max-Age, вы можете управлять сроком действия файла cookie.

Пример добавления SameSite / Secure в браузере (что возможно)

HttpOnly нельзя установить на стороне клиента. Ниже приведён пример добавления SameSite и Secure на стороне клиента. Однако Secure действует только на страницах, доступных по HTTPS.

 1// Set cookie with SameSite and Secure attributes (Secure only effective over HTTPS).
 2const setCookieWithAttributes = (name: string, value: string) => {
 3  const maxAge = 60 * 60 * 24 * 7; // 7 days in seconds
 4  // Note: HttpOnly cannot be set from JS; set it on server-side when you want to restrict JS access.
 5  document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; `
 6    + `Max-Age=${maxAge}; `
 7    + `Path=/; `
 8    + `SameSite=Lax; `
 9    + `Secure`;
10};
11
12setCookieWithAttributes('session_hint', 'true');
  • SameSite=Lax — безопасный и распространенный вариант. Для более строгих ограничений используйте SameSite=Strict, но из-за этого легитимные переходы с внешних сайтов могут перестать работать.

Установка файлов cookie на сервере (Set-Cookie) (Node / TypeScript)

На сервере можно добавить HttpOnly через заголовок Set-Cookie, поэтому управление сессиями в идеале следует выполнять на стороне сервера. Ниже приведен пример с использованием модуля http в Node.

 1// A minimal Node HTTP server in TypeScript that sets a secure HttpOnly cookie.
 2// This example uses built-in 'crypto' for a random session id.
 3import http from 'http';
 4import crypto from 'crypto';
 5
 6const server = http.createServer((req, res) => {
 7  if (req.url === '/login') {
 8    const sessionId = crypto.randomBytes(16).toString('hex');
 9    // Set cookie with HttpOnly, Secure, SameSite and Path
10    // Expires is optional — Max-Age preferred for relative lifetimes.
11    res.setHeader('Set-Cookie', `sid=${sessionId}; `
12      + `HttpOnly; `
13      + `Secure; `
14      + `SameSite=Strict; `
15      + `Path=/; `
16      + `Max-Age=3600`
17    );
18    res.writeHead(302, { Location: '/' });
19    res.end();
20    return;
21  }
22
23  res.writeHead(200, { 'Content-Type': 'text/plain' });
24  res.end('Hello\n');
25});
26
27server.listen(3000, () => {
28  console.log('Server running on http://localhost:3000');
29});
  • Когда вы устанавливаете атрибут HttpOnly на стороне сервера, файл cookie становится недоступным из JavaScript, что затрудняет его кражу при XSS-атаках (межсайтовом скриптинге). Кроме того, добавив атрибут Secure, чтобы файлы cookie всегда передавались по HTTPS, можно предотвратить перехват и подмену и повысить безопасность передачи данных.

Реализация сериализации/разбора файлов cookie (серверный помощник)

Ниже приведена простая серверная утилита, формирующая строку заголовка Set-Cookie.

 1// Cookie serialization helper for server-side use.
 2// Returns a properly formatted Set-Cookie header value.
 3type CookieOptions = {
 4  path?: string;
 5  domain?: string;
 6  maxAge?: number;
 7  expires?: Date;
 8  httpOnly?: boolean;
 9  secure?: boolean;
10  sameSite?: 'Strict' | 'Lax' | 'None';
11};
12
13const serializeCookie = (name: string, value: string, opts: CookieOptions = {}): string => {
14  const parts: string[] = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
15
16  if (opts.maxAge != null) parts.push(`Max-Age=${Math.floor(opts.maxAge)}`);
17  if (opts.expires) parts.push(`Expires=${opts.expires.toUTCString()}`);
18  if (opts.domain) parts.push(`Domain=${opts.domain}`);
19  parts.push(`Path=${opts.path ?? '/'}`);
20  if (opts.httpOnly) parts.push('HttpOnly');
21  if (opts.secure) parts.push('Secure');
22  if (opts.sameSite) parts.push(`SameSite=${opts.sameSite}`);
23
24  return parts.join('; ');
25};
26
27// Example usage:
28const headerValue = serializeCookie('uid', 'abc123', {
29  httpOnly: true,
30  secure: true,
31  sameSite: 'Lax',
32  maxAge: 3600
33});
34console.log(headerValue);
35// => "uid=abc123; Max-Age=3600; Path=/; HttpOnly; Secure; SameSite=Lax"
  • Эта утилита служит основой для формирования Set-Cookie на кастомном сервере. Библиотеки способны обрабатывать значительно больше крайних случаев.

Предотвращение подделки с помощью подписей (HMAC)

Опасно хранить важные значения непосредственно в файлах cookie. Рассмотрим метод, при котором значения подписываются на сервере для обнаружения подделки. Здесь мы используем HMAC-SHA256. В продакшене также необходимо управлять отзывом токенов на стороне сервера.

 1// A simple HMAC signing and verification helper for cookies.
 2// Signing prevents client from tampering cookie values.
 3import crypto from 'crypto';
 4
 5const SECRET = 'replace_with_env_secret'; // store in env var in production
 6
 7const signValue = (value: string) => {
 8  const hmac = crypto.createHmac('sha256', SECRET);
 9  hmac.update(value);
10  return `${value}.${hmac.digest('hex')}`;
11};
12
13const verifySignedValue = (signed: string): string | null => {
14  const idx = signed.lastIndexOf('.');
15  if (idx === -1) return null;
16  const value = signed.slice(0, idx);
17  const sig = signed.slice(idx + 1);
18
19  const hmac = crypto.createHmac('sha256', SECRET);
20  hmac.update(value);
21  const expected = hmac.digest('hex');
22  // use timing-safe comparison in production
23  if (crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
24    return value;
25  }
26  return null;
27};
28
29// Example usage:
30const signed = signValue('userId=42');
31console.log('signed:', signed);
32console.log('verified:', verifySignedValue(signed)); // => "userId=42"
  • С помощью этого метода можно проверить, было ли значение файла cookie подделано. Не включайте ключ подписи в исходный код; управляйте им через безопасные средства, такие как переменные окружения.

Файлы cookie и снижение риска CSRF (общие подходы)

Сессии, использующие файлы cookie, нуждаются в защите от CSRF. К типичным мерам относятся:.

  • Установите SameSite в Lax или Strict, чтобы предотвратить ненужную кросс-сайтовую отправку. Используйте SameSite=None; Secure только если необходимо кросс-оригинное взаимодействие.
  • Используйте CSRF-токены, проверяемые сервером, для форм и запросов к API. Не храните токен в cookie с флагом HttpOnly; вместо этого передавайте его в теле ответа или через мета-теги, и пусть JavaScript считывает его только при необходимости.
  • Если к токену есть доступ из JavaScript, обязательно реализуйте защиту от XSS (CSP, экранирование вывода и т. п.).
  • Требуйте повторной аутентификации для чувствительных действий и при входе в систему, а также предотвращайте атаки фиксации сеанса.

Ниже приведен безопасный пример клиента, который отправляет CSRF-токен в заголовке. Предполагается, что клиент уже получил токен от сервера, например, в теле ответа.

 1// Example of sending CSRF token safely in a header using fetch.
 2// Assumes CSRF token was provided securely (e.g., via response body or meta tag).
 3async function sendProtectedRequest(url: string, csrfToken: string) {
 4  const res = await fetch(url, {
 5    method: 'POST',
 6    credentials: 'same-origin', // include cookies for same-site requests
 7    headers: {
 8      'Content-Type': 'application/json',
 9      'X-CSRF-Token': csrfToken
10    },
11    body: JSON.stringify({ action: 'doSomething' })
12  });
13  return res;
14}
  • Если указать credentials: 'same-origin', будут отправляться только cookie того же источника (origin). На сервере проверьте значение заголовка X-CSRF-Token и убедитесь, что токен совпадает. Если межсайтовые запросы необходимы, тщательно настройте CORS.

Кросс-сайт (CORS) и связь с credentials

Чтобы отправлять и получать cookie при кросс-оригинном взаимодействии, клиент должен указать credentials: 'include', а сервер должен установить Access-Control-Allow-Credentials: true. Однако ограничьте эту конфигурацию доверенными источниками и не используйте Access-Control-Allow-Origin: *.

 1async function fetchCrossOriginData() {
 2  // Example: cross-origin fetch sending cookies.
 3  // Server must set Access-Control-Allow-Credentials: true
 4  // and a specific trusted origin.
 5  const res = await fetch('https://api.example.com/data', {
 6    credentials: 'include', // send cookies only to trusted domains
 7    method: 'GET'
 8  });
 9  return res;
10}
  • Сочетание CORS и cookie весьма чувствительно с точки зрения безопасности. Жестко ограничивайте разрешенные источники с помощью белого списка и избегайте ненужного межсайтового взаимодействия. Кроме того, при использовании SameSite=None; Secure принудительно применяйте HTTPS, чтобы предотвратить атаки типа «человек посередине».

Пример: безопасные настройки сессионного файла cookie в Express (TypeScript)

На практике используйте Express, библиотеку cookie, express-session и т. п. Ниже простой пример с использованием express и cookie-parser. На практике установите secure в true и управляйте secret через переменные окружения.

 1// Express example using cookie-parser and setting a secure httpOnly cookie.
 2// npm install express cookie-parser @types/express @types/cookie-parser
 3import express from 'express';
 4import cookieParser from 'cookie-parser';
 5
 6const app = express();
 7app.use(cookieParser(process.env.COOKIE_SECRET));
 8
 9app.post('/login', (req, res) => {
10  // authenticate user (omitted)
11  const sessionId = 'generated-session-id';
12  res.cookie('sid', sessionId, {
13    httpOnly: true,
14    secure: true,        // require HTTPS in production
15    sameSite: 'lax',
16    maxAge: 1000 * 60 * 60 // 1 hour
17  });
18  res.json({ ok: true });
19});
20
21app.listen(3000);
  • Используя возможность secret у cookieParser, можно легко работать с подписанными cookie. Однако в реальных приложениях, с точки зрения безопасности и масштабируемости, не следует хранить данные непосредственно в cookie; вместо этого используйте отдельное хранилище сессий.

Префиксы файлов cookie __Host- и __Secure-

Браузеры применяют особые правила для некоторых префиксов.

  • Префикс __Secure- Если имя файла cookie начинается с __Secure-, атрибут Secure обязателен.
  • Префикс __Host- Если оно начинается с __Host-, атрибут Secure обязателен, путь должен быть / (корень), а Domain не должен быть задан.

Использование таких префиксов снижает риск неправильной конфигурации и повышает безопасность.

Рекомендации по работе с файлами cookie

Для безопасной обработки файлов cookie учтите следующие моменты.

  • Не храните конфиденциальную информацию непосредственно в файлах cookie. Для токенов доступа предпочтителен серверный идентификатор сессии.
  • Устанавливайте сессионные файлы cookie с атрибутами HttpOnly, Secure и SameSite=Lax (или Strict).
  • Используйте префиксы __Host- и __Secure-.
  • Рассмотрите использование подписей (HMAC) и шифрования для предотвращения подделки и перехвата.
  • Включайте Secure и требуйте HTTPS.
  • Соблюдайте принцип наименьших привилегий и устанавливайте короткие сроки жизни.
  • Используйте CSRF-токены.
  • Учитывайте различия в поведении SameSite в разных браузерах, особенно старых.

Распространенные заблуждения о файлах cookie

В отношении файлов cookie имейте в виду следующие распространённые заблуждения.

  • 'Добавление HttpOnly устраняет последствия XSS.' Хотя HttpOnly не позволяет JavaScript получить доступ к файлам cookie, XSS всё ещё можно использовать для отправки произвольных запросов. Следует также использовать проверку CSRF-токенов, CSP (Content Security Policy) и очистку входных данных.
  • 'Secure не нужен для локальной разработки.' Моделирование HTTPS и проверка поведения даже в локальной среде повышают точность тестирования. Как минимум, в промежуточной (staging) и производственной (production) средах следует использовать HTTPS.
  • 'Длительные сроки действия удобны.' Если вы устанавливаете длительные сроки жизни файлов cookie, увеличивается период, в течение которого ими могут злоупотреблять в случае их кражи. Можно установить более короткие сроки действия и внедрить периодическую повторную аутентификацию и ротацию токенов.

Резюме

Хотя файлы cookie просты в использовании, неправильное обращение с ними может привести к уязвимостям безопасности. Чтобы правильно управлять ими в TypeScript, важно понимать такие атрибуты, как HttpOnly, Secure и SameSite, и обеспечивать безопасные серверные настройки. Отказавшись от прямого хранения конфиденциальных данных и сочетая подписи с короткими сроками жизни, можно добиться безопасного и надежного управления сессиями.

Вы можете следовать этой статье, используя Visual Studio Code на нашем YouTube-канале. Пожалуйста, также посмотрите наш YouTube-канал.

YouTube Video