W3docs

JavaScript Fetch: межсайтовые запросы (CORS)

Межсайтовые запросы с Fetch API: как работает CORS, опции mode и credentials, preflight-запросы и лучшие практики обработки ошибок.

Межсайтовые запросы позволяют веб-странице загружать данные с сервера, расположенного на другом источнике, чем сама страница. Они лежат в основе почти каждого современного приложения: обращение к публичному API, взаимодействие с собственным бэкендом на другом поддомене или встраивание стороннего сервиса. На этой странице объясняется, как работают правила CORS в браузере, как выполнять межсайтовые запросы с помощью Fetch API, а также как правильно работать с учётными данными, preflight-запросами и ошибками.

Что считается «межсайтовым»

Источник (origin) — это комбинация протокола + хоста + порта. Два URL имеют один и тот же источник только тогда, когда все три компонента совпадают. Если хотя бы один из них отличается, запрос между ними является межсайтовым.

Запрос с https://app.example.com на…Один источник?Почему
https://app.example.com/api/usersДаодинаковые протокол, хост, порт
http://app.example.com/apiНетразные протоколы (http vs https)
https://api.example.com/usersНетразные хосты (поддомен считается другим)
https://app.example.com:8443/apiНетразные порты

Запросы в пределах одного источника не имеют ограничений. Межсайтовые запросы регулируются правилами CORS.

Что такое CORS (и чем он не является)

Cross-Origin Resource Sharing (CORS) — это механизм безопасности браузера, который определяет, разрешено ли JavaScript на одном источнике читать ответ с другого источника. Решение принимает сервер, который явно разрешает это, отправляя специальные HTTP-заголовки ответа. Браузер затем применяет эти правила.

Две вещи легко перепутать:

  • CORS — это не то, что можно исправить только во фронтенд-коде. Если сервер не отправляет нужные заголовки, никакая опция fetch не позволит успешно прочитать ответ.
  • Запрос зачастую всё равно доходит до сервера и выполняется там; CORS лишь запрещает скрипту читать ответ. Именно поэтому CORS не является заменой аутентификации.

Межсайтовый запрос с помощью Fetch

Fetch API — это современный способ выполнения HTTP-запросов на основе промисов. Обычный вызов fetch к другому источнику уже является межсайтовым — браузер обрабатывает CORS автоматически.

javascript— editable

Это работает, потому что jsonplaceholder.typicode.com возвращает Access-Control-Allow-Origin: *. Если бы этого не было, браузер заблокировал бы чтение ответа, и fetch вернул бы ошибку. Подробнее о модели запрос/ответ читайте в главе Fetch API.

Заголовки сервера, которые это обеспечивают

При межсайтовом запросе сервер должен включить заголовки CORS, разрешающие его. Наиболее часто встречающиеся:

  • Access-Control-Allow-Origin — какой источник (или источники) может читать ответ (конкретный источник или * для любого).
  • Access-Control-Allow-Methods — какие HTTP-методы разрешены (используется в ответах на preflight).
  • Access-Control-Allow-Headers — какие заголовки запроса может отправлять клиент (используется в ответах на preflight).
  • Access-Control-Allow-Credentials — разрешена ли отправка cookies и данных авторизации (должно быть true для разрешения учётных данных).

Эти параметры настраиваются на сервере, а не в fetch. Фронтенд управляет только запросом.

Опция mode

Опция mode в Fetch определяет поведение при межсайтовых запросах:

  • 'cors' — значение по умолчанию. Запрос разрешается только если сервер возвращает соответствующие заголовки CORS; в противном случае чтение блокируется.
  • 'same-origin' — немедленно завершается ошибкой для любого межсайтового URL.
  • 'no-cors' — отправляет запрос, но возвращает непрозрачный ответ: вы не можете прочитать его статус, заголовки или тело. Полезно только для сценариев «отправил и забыл», например при кешировании изображения в сервис-воркере.

Поскольку 'cors' и так является значением по умолчанию, явно указывать его почти никогда не нужно, однако это делает намерение кода очевидным:

Внимание

На сервере избегайте Access-Control-Allow-Origin: * в рабочей среде. Разрешайте только те источники, которым вы доверяете.

javascript— editable

Отправка учётных данных

По умолчанию межсайтовые запросы fetch не отправляют cookies или заголовки HTTP-аутентификации. Чтобы включить их, установите опцию credentials в 'include'. Сервер также должен ответить с Access-Control-Allow-Credentials: true и указать ваш конкретный источник в Access-Control-Allow-Origin — значение * отклоняется при использовании учётных данных. Подробнее о том, как cookies работают между сайтами, читайте в главе Cookies и document.cookie.

async function fetchWithCredentials(url) {
  try {
    const response = await fetch(url, {
      mode: 'cors',
      credentials: 'include'
    });
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Fetch error:', error);
  }
}

fetchWithCredentials('https://api.crossorigin.com/secure-data');
Примечание

Когда политика CORS блокирует межсайтовое чтение, браузер не передаёт скрипту ответ. Вместо этого fetch отклоняется с TypeError («Failed to fetch»), и ошибка попадает в блок catch — но не в проверку if (!response.ok).

Preflight-запросы

Для запросов, которые браузер считает потенциально небезопасными, он сначала автоматически отправляет запрос OPTIONSpreflight — чтобы спросить у сервера, разрешён ли реальный запрос. Ваш код никогда не отправляет этот вызов OPTIONS самостоятельно — это делает браузер.

Запрос вызывает preflight, если он не является «простым запросом», то есть когда он:

  • использует метод, отличный от GET, HEAD или POST;
  • включает пользовательские заголовки запроса (например Authorization или X-Api-Key);
  • использует Content-Type, отличный от application/x-www-form-urlencoded, multipart/form-data или text/plain (отправка JSON с application/json — наиболее распространённый случай).

Типичный JSON-запрос POST поэтому требует двух обращений к серверу — preflight, а затем реальный запрос:

async function createPost(url, payload) {
  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' }, // triggers a preflight
      body: JSON.stringify(payload)
    });
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    console.error('Fetch error:', error);
  }
}

createPost('https://api.example.com/posts', { title: 'Hello' });

Чтобы preflight прошёл успешно, сервер должен ответить на запрос OPTIONS, включив Access-Control-Allow-Methods и Access-Control-Allow-Headers, охватывающие то, что будет использоваться в реальном запросе. Если проверка не пройдена, браузер генерирует сетевую ошибку, и реальный запрос так и не отправляется.

Различие типов ошибок

Ошибки CORS печально известны своей запутанностью, поскольку браузер скрывает подробности в целях безопасности. Используйте следующий чек-лист:

  • fetch отклоняется («Failed to fetch») — как правило, это блокировка CORS, сетевой сбой или неудавшийся preflight. Проверьте консоль браузера для получения конкретного сообщения об ошибке CORS — оно недоступно в JavaScript.
  • response.ok равно false — запрос выполнен успешно и CORS пройден, но сервер вернул статус 4xx/5xx. Это ошибка приложения, а не ошибка CORS.
  • Cookies не отправляются — вы забыли указать credentials: 'include', или сервер не возвращает Access-Control-Allow-Credentials: true с конкретным источником.

Оборачивайте вызовы в try/catch и отдельно проверяйте response.ok, как это сделано в приведённых примерах.

Лучшие практики

  • Ограничьте Access-Control-Allow-Origin. Перечисляйте конкретные источники, которым вы доверяете, вместо * — особенно если используются учётные данные (* отклоняется при их наличии).
  • Всегда используйте HTTPS. Это защищает данные при передаче и требуется многими API безопасного контекста.
  • Сокращайте количество preflight-запросов. Избегайте лишних пользовательских заголовков и настройте на сервере Access-Control-Max-Age, чтобы браузер кешировал результаты preflight.
  • Обрабатывайте ошибки корректно. Разделяйте транспортные/CORS-ошибки (catch) и ошибки HTTP-статуса (!response.ok) и предоставляйте пользователю понятные сообщения.
  • Не полагайтесь на CORS как на средство безопасности. Он контролирует лишь то, что браузеры позволяют скриптам читать; он не аутентифицирует вызывающую сторону. Проверяйте и авторизуйте каждый запрос на стороне сервера.

Связанные главы

  • Fetch API — основа каждого запроса на этой странице.
  • Отмена fetch — отмена медленных или ненужных запросов.
  • Работа с JSON — разбор и сериализация тел запросов и ответов.
  • Cookies: document.cookie — как cookies взаимодействуют с межсайтовыми учётными данными.

Итог

Запрос является межсайтовым, если его протокол, хост или порт отличается от параметров страницы. CORS позволяет серверу решать, какие источники могут читать ответ, а браузер применяет это решение. В Fetch режим 'cors' используется по умолчанию; опция credentials позволяет отправлять cookies, для нестандартных вызовов ожидайте preflight-запрос OPTIONS, а ошибки CORS обрабатывайте в catch, проверяя response.ok для ошибок на уровне HTTP.

Was this page helpful?