CORS preflight не проходит: как проверить OPTIONS до основного запроса

OPTIONS возвращает 401 или 403, хотя API отвечает из терминала? Разбираем предварительный запрос, разрешения CORS и проверку исправления без отключения авторизации.

Если браузер сообщает об ошибке CORS, а OPTIONS возвращает 401 или 403, сначала проверьте предварительный запрос — preflight. Его отказ может остановить отправку основного запроса: успешный вызов API из терминала этого не опровергает.

Задача диагностики — найти, кто отклонил OPTIONS, и сверить разрешения для конкретной страницы, метода и заголовков. Не отключайте авторизацию API: разрешение предварительной проверки не даёт пользователю права читать или менять данные.

1. Найдите именно предварительный запрос

В инструментах разработчика откройте Network до повторения ошибки, включите сохранение журнала и найдите URL API. Смотрите все типы запросов: фильтр только по Fetch/XHR может скрыть нужную строку.

Зафиксируйте четыре значения:

  • адрес запроса;
  • Origin — схему, домен и порт страницы, которая обращается к API;
  • Access-Control-Request-Method — метод будущего запроса;
  • Access-Control-Request-Headers — имена заголовков, которые браузер хочет отправить.

Например, POST с JSON на другой origin обычно требует preflight. Сам OPTIONS не содержит JSON заказа: браузер предварительно спрашивает сервер о разрешении. Этот механизм описан в справке MDN о preflight.

Если основной запрос уже виден и получил ответ, рассматривайте его отдельно: причина может быть в CORS-заголовках ответа или авторизации самой операции. Отсутствие OPTIONS тоже не доказывает сбой: предварительная проверка нужна не всегда, а её успешный результат браузер может кешировать.

2. По ответу определите следующий шаг

| Ответ OPTIONS | Что проверить | |---|---| | 401 | Не требует ли шлюз или middleware входа до обработки preflight | | 403 | Правила разрешённых origin, ограничения WAF и журнал отказов | | 404 или 405 | Дошёл ли OPTIONS до нужного маршрута и кто запретил метод | | 301 или 302 | Нет ли перенаправления на вход, HTTPS или другой путь | | 200 или 204 | Совпадают ли CORS-разрешения с запросом браузера |

Код — направление поиска, а не доказательство причины. Сопоставьте время и путь с журналами CDN, прокси и приложения: запись на внешнем слое без записи в приложении помогает сузить участок отказа. Если запрещён сам метод, пригодится отдельный разбор ошибки 405.

Предварительный CORS-запрос не должен зависеть от пользовательских cookie или bearer-токена. Для разрешённых origin обработку OPTIONS настраивают до проверки сессии, сохраняя авторизацию основного запроса. При намеренном запрете чужого origin отказ исправлять не требуется.

3. Сверьте разрешения, даже если код успешный

Для примера: страница https://app.example.com хочет отправить POST с заголовками authorization и content-type. Проверяйте ответ API по списку:

  1. Access-Control-Allow-Origin содержит разрешённый origin страницы; различия в схеме и порте существенны.
  2. Access-Control-Allow-Methods разрешает нужный метод.
  3. Access-Control-Allow-Headers разрешает запрошенные заголовки; Authorization указывают явно.
  4. Если основной fetch использует credentials: "include", нужны Access-Control-Allow-Credentials: true и конкретный origin вместо *.

Не отражайте произвольный Origin без проверки по списку разрешений. Посмотрите, не добавляют ли приложение и прокси два конфликтующих значения Access-Control-Allow-Origin. Если значение выбирается по Origin и ответ кешируется, проверьте корректное разделение кеша через Vary: Origin.

Успешного HTTP-кода без подходящих разрешений недостаточно; правила проверки определяет стандарт Fetch. CORS-заголовки потребуются и в ответе на основной запрос, который должен быть доступен JavaScript.

4. Воспроизведите OPTIONS без секретов

На своём API можно отдельно посмотреть HTTP-ответ. Ниже условные адреса: замените их и перечень заголовков значениями из Network.

curl -i -X OPTIONS 'https://api.example.com/items' \
  -H 'Origin: https://app.example.com' \
  -H 'Access-Control-Request-Method: POST' \
  -H 'Access-Control-Request-Headers: authorization,content-type'

Здесь указано только имя authorization, а не токен. Cookie, пароль и тело пользовательской операции не нужны. Не добавляйте -L: сначала важно увидеть исходный редирект, если он есть.

curl показывает серверный ответ, но не применяет браузерную политику CORS. Поэтому совпадение кодов полезно для сравнения, а окончательная проверка проходит в браузере с исходной страницы. Переключение fetch в no-cors не делает JSON доступным скрипту; ограничения и работу с credentials поясняет руководство MDN по CORS.

5. Подтвердите исправление на обоих этапах

После изменения повторите проверку в новом браузерном сеансе, чтобы старое разрешение preflight не скрыло результат. Используйте тестовую операцию без реального заказа, оплаты или отправки письма.

Исправление подтверждено, когда разрешённая страница проходит OPTIONS, основной запрос отправляется и JavaScript получает ожидаемый результат. Отдельно убедитесь, что без необходимых прав основной запрос по-прежнему отклоняется. Сохраните время проверки, origin, метод и обезличенные заголовки ответа для сравнения после следующего релиза.

Web-Puls помогает контролировать внешнюю доступность безопасного публичного URL, но такая проверка не воспроизводит CORS-контекст страницы. Выбор контрольной точки разобран в руководстве по мониторингу API.

Если отказ возникает между прокси и приложением и причину не удаётся локализовать, можно отправить заявку на диагностику. Укажите публичный URL без секретов, время ошибки, origin, код OPTIONS и недавние изменения; это даст исходные данные для обсуждения задачи.

Проверьте свой сайт прямо сейчас

Введите адрес сайта: Web-Puls покажет HTTP-код, время ответа и базовую диагностику. Для постоянного контроля можно подключить мониторинг.

Нужна помощь с диагностикой ошибки?

Опишите симптомы в короткой заявке: URL, код ответа, время появления и что менялось перед сбоем. Специалист поддержки оценит задачу и предложит формат работ.