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 не решают их все.