TATECHATLAS
◎ Русский
Web и API

curl работает, fetch не работает: диагностика CORS и предварительных запросов

Отличайте доступ к ответу от отправки запроса, проверяйте OPTIONS и настраивайте явные источники для кросс-доменных запросов с учётными данными.

В этом материале

curl не применяет правила браузерного CORS. Успешный ответ curl не означает, что JavaScript на другом источнике сможет его прочитать. Проверяйте панель Network в браузере: некоторые запросы отправляются напрямую, а запросы, например JSON POST или с Authorization, обычно требуют предварительный OPTIONS. Проверяйте предварительный запрос и ответ отдельно, используя точное происхождение страницы и предполагаемый метод и заголовки.

Происхождение - это схема, хост и порт

Страница https://app.example и API https://api.example имеют разные происхождения, хотя их имена имеют общий суффикс. Изменение порта или переход от HTTP к HTTPS также может изменить происхождение. Начните с фактического заголовка Origin в браузере, а не угадывайте по имени развёртывания.

CORS регулирует то, какие кросс-доменные ответы браузер предоставляет скрипту. Это не аутентификация API и не останавливает curl или другой сервер вызывать API. Оставьте авторизацию и защиту от нежелательных изменяющих состояние запросы в приложении.

Прямые запросы и предварительные запросы - разные пути

GET без небезопасных заголовков запроса часто можно отправлять без предварительного запроса. Определённые POST-запросы с типами содержимого, совместимыми с формой, также могут использовать этот путь. Ответ всё равно нуждается в соответствующих заголовках CORS, прежде чем JavaScript сможет его прочитать.

Запрос, использующий application/json, Authorization или метод, например PUT, обычно требует предварительного запроса. Браузер сначала спрашивает, какие методы и заголовки запроса разрешены, прежде чем отправлять приложение-запрос. Само по себе учётное имя не означает, что каждый запрос должен быть предварительным; проверяйте полные условия запроса.

Конкретный пример JSON POST

Предположим, страница на https://app.example отправляет JSON POST на https://api.example/items с bearer-токеном. Это иллюстративные хосты и гипотетический конечный пункт, а не рабочий сервис. Приведённый код находится на странице браузера; токен - заполнитель.

Без действительного кэшированного результата предварительного запроса ожидается OPTIONS-запрос, объявляющий POST и заголовки авторизации и content-type. Браузер формирует эти предварительные заголовки; приложение JavaScript не должно устанавливать их вручную.

fetch('https://api.example/items', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer DEMO_TOKEN'
  },
  body: JSON.stringify({name: 'sample'})
}).then(response => {
  if (!response.ok) throw new Error('HTTP ' + response.status);
  return response.json();
}).then(console.log).catch(console.error);

Сначала прочитайте предварительный запрос, прежде чем менять настройки сервера

В инструментах разработчика сохраняйте журнал сетевых запросов и ищите OPTIONS. Его Origin должен указывать на страницу, а Access-Control-Request-Method в этом примере должен быть POST. Access-Control-Request-Headers перечисляет запрошенные небезопасные заголовки. Регистр имён заголовков не имеет значения.

Подходящий успешный ответ предварительного запроса разрешает конкретное происхождение, метод и заголовки. OPTIONS не должен требовать bearer-токен, предназначенный для последующего POST, потому что предварительный запрос не содержит этого заголовка приложения. Аутентификация всё ещё необходима для фактического запроса.

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: authorization, content-type
Vary: Origin

Фактический ответ нуждается в своей собственной разрешающей политике

Успешное прохождение предварительного запроса - только первый шаг. Ответ POST также нуждается в Access-Control-Allow-Origin. Проверяйте не только успешные ответы, но и ошибки: ошибка аутентификации без соответствующего заголовка CORS может выглядеть как обычная сетевая ошибка для JavaScript.

Если OPTIONS успешен, а POST не удался, проверяйте его статус, логи приложения и заголовки ответа. Сообщение CORS не доказывает, что аутентификация или логика приложения успешны. Наоборот, напрямую отправленный запрос может достичь сервера, даже если браузер блокирует скрипт доступ к его ответу.

Cookies требуют явной обработки происхождения

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

При отражении входящего происхождения используйте белый список; слепое отражение каждого происхождения даёт слишком широкий доступ. Когда ответ варьируется по Origin, Vary: Origin помогает кэшам разделять эти варианты. Сопоставляйте происхождение точно; пути не являются частью происхождения.

Сравните запрос браузера с командной строкой

Вызов командной строки может помочь проверить HTTP-статус и возвращённые заголовки, но он не воспроизводит принудительное применение браузером. Сравнивайте метод, Origin, заголовки запроса, перенаправления и обработку учётных данных. Успешный простой GET не диагностирует неудачный аутентифицированный JSON POST.

Не используйте mode: no-cors для получения читаемых данных API. Это может привести к непрозрачному ответу, тело и статус которого недоступны скрипту. Вместо этого исправляйте разрешения сервера для предполагаемого запроса, не считая непрозрачный результат успешной интеграцией.

Краткий порядок диагностики

Сначала подтвердите происхождения страницы и API. Затем отделите прямой запрос от OPTIONS, за которым следует фактический метод. Проверяйте разрешённое происхождение, метод и заголовки на предварительном запросе; проверяйте разрешение происхождения на ответе приложения. Наконец, проверяйте аутентификацию и политику cookies.

Кэширование предварительных запросов может подавлять запрос OPTIONS, который появлялся ранее. Не выводите вывод, что запрос стал простым просто потому, что в журнале нет новой записи OPTIONS. Эта последовательность сужает сбой до конкретного обмена без ослабления контроля доступа API.

Что проверить

  • Запишите точное происхождение браузера, метод и запрашиваемые заголовки.
  • Проверяйте как OPTIONS, так и фактический ответ, включая ошибки.
  • Используйте явное разрешённое происхождение для ответов с учётными данными.
  • Сохраняйте аутентификацию и защиту от подделки запросов отдельно от CORS.

Это руководство охватывает обычные запросы fetch. Перенаправления, политика cookies, сетевые сбои и аутентификация приложения могут вызывать дополнительные сбои; заголовки CORS не решают их все.

Источники

  1. MDN: CORS ↗
  2. MDN: preflight request ↗
Наверх ↑