Fetch: headers, mode, credentials

Параметры headers, mode и credentials в Fetch API управляют заголовками запроса, политикой CORS и передачей куки/токенов.

Зачем нужно

Дефолтный fetch(url) работает для простых GET-запросов на тот же домен. Как только появляется авторизация, кросс-доменные запросы или нестандартные заголовки — нужно явно указывать эти параметры. Непонимание credentials приводит к тому, что куки и сессии не передаются; неверный mode ломает CORS-запросы.

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

  • Авторизованные запросы к API (JWT, session cookies)
  • Кросс-доменные запросы (CORS) к сторонним API
  • Preflight-запросы с нестандартными заголовками
  • Загрузка ресурсов с другого домена (изображения, шрифты)

headers

// Объект заголовков — самый простой способ
const res = await fetch('/api/data', {
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer eyJhbG...',
    'X-Request-Id': crypto.randomUUID,
  },
});

// Headers API — позволяет проверять и добавлять заголовки
const headers = new Headers();
headers.set('Authorization', `Bearer ${token}`);
headers.append('Accept-Language', 'ru');
const res2 = await fetch('/api/profile', { headers });

// Чтение заголовков ответа
console.log(res.headers.get('Content-Type'));   // application/json
console.log(res.headers.get('X-Total-Count'));  // 156

mode

// 'cors' (по умолчанию) — запрос с CORS-проверкой
// Работает, если сервер вернул Access-Control-Allow-Origin
const res = await fetch('https://api.example.com/data', { mode: 'cors' });

// 'same-origin' — запрос только на тот же домен; выбросит ошибку иначе
const res2 = await fetch('/api/internal', { mode: 'same-origin' });

// 'no-cors' — запрос без CORS-заголовков, ответ «непрозрачный» (opaque)
// Тело недоступно — используется только для отправки «пинга»
await fetch('https://analytics.example.com/ping', {
  mode: 'no-cors',
  method: 'POST',
  body: JSON.stringify({ event: 'pageview' }),
});

credentials

// 'omit' — никогда не отправлять куки
const res = await fetch('https://api.example.com/data', { credentials: 'omit' });

// 'same-origin' — куки только для одного домена (по умолчанию)
const res2 = await fetch('/api/profile', { credentials: 'same-origin' });

// 'include' — всегда отправлять куки, в т.ч. кросс-доменные
// Требует: Access-Control-Allow-Credentials: true
//          Access-Control-Allow-Origin: <точный origin> (не '*')
const res3 = await fetch('https://api.example.com/profile', {
  credentials: 'include',
});

Паттерн авторизованного API-клиента

const api = {
  baseUrl: 'https://api.example.com',
  async request(path, options = {}) {
    const token = localStorage.getItem('token');
    const res = await fetch(`${this.baseUrl}${path}`, {
      mode: 'cors',
      credentials: 'include',
      headers: {
        'Content-Type': 'application/json',
        ...(token && { Authorization: `Bearer ${token}` }),
        ...options.headers,
      },
      ...options,
    });
    if (res.status === 401) { window.location.href = '/login'; return; }
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json();
  },
  get: (path, opts) => api.request(path, { ...opts, method: 'GET' }),
  post: (path, body, opts) =>
    api.request(path, { ...opts, method: 'POST', body: JSON.stringify(body) }),
};

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

  • Не указывают credentials: 'include' — куки сессии не передаются в кросс-доменных запросах
  • Не устанавливают Content-Type: application/json при отправке JSON
  • Ожидают прочитать тело no-cors-ответа — он всегда opaque
  • Сочетают Access-Control-Allow-Origin: * с credentials: 'include' — браузер запрещает эту комбинацию

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

Ресурсы