ФронтендБэкендДругой язык

Ошибка CORS в браузере: что заблокировано и почему правка фронтенда не всегда помогает

Разбираем CORS: origin, preflight OPTIONS, серверные заголовки, credentials и почему no-cors или расширение браузера не исправляют API.

Кодик

Автор

5 мин чтения

Ошибка CORS означает, что браузер не разрешил JavaScript прочитать ответ другого origin по действующей политике сервера. Чаще всего исправление находится в настройке API или доверенного прокси, а не в строке fetch. Сначала запишите origin страницы и origin API, затем найдите запрос во вкладке Network. Проверьте, отправлялся ли OPTIONS, какой заголовок Origin ушёл и какие Access-Control-Allow-* вернул сервер.

CORS расшифровывается как Cross-Origin Resource Sharing. Это HTTP-механизм, с помощью которого сервер сообщает браузеру, каким внешним источникам разрешён доступ к ответу. Запрос может физически дойти до API и даже получить 200, но браузер всё равно не отдаст тело вашему JavaScript, если политика не совпала.

1Сравнить origin

Схема, хост и порт должны совпасть, иначе запрос считается cross-origin.

2Открыть Network

Посмотреть реальный запрос, preflight и ответные заголовки.

3Исправить владельца

Разрешение обычно настраивается на API, gateway или backend-прокси.

Что браузер считает другим origin

Origin состоит из схемы, имени хоста и порта. Страницы http://localhost:5173 и http://localhost:3000 находятся на одном компьютере, но имеют разные порты и потому разные origin. Путь не входит в origin: /users и /orders на одном протоколе, хосте и порту остаются same-origin.

Страница: http://localhost:5173
API:      http://localhost:3000

Схема: http = http
Хост: localhost = localhost
Порт: 5173 != 3000
Результат: cross-origin request
СтраницаAPIРезультат
https://site.testhttps://site.test/apiSame-origin
https://site.testhttp://site.test/apiДругая схема
https://site.testhttps://api.site.testДругой хост
http://localhost:5173http://localhost:3000Другой порт
https://site.test/ahttps://site.test/bSame-origin, пути не важны

Сравнение схемы хоста и порта двух origin
Различие любого из трёх компонентов делает запрос cross-origin.

localhost и 127.0.0.1 являются разными хостами. Даже если оба адреса ведут на текущую машину, браузер сравнивает строки компонентов origin. Выберите одно имя и используйте его последовательно в адресе фронтенда, настройке API и разрешённом списке источников.

Как читать CORS-ошибку во вкладке Network

Диагностика без догадок
  1. Откройте DevTools, вкладку Network и включите сохранение лога при перезагрузке.
  2. Повторите действие и найдите запрос к API по URL или методу.
  3. Запишите Origin из Request Headers.
  4. Проверьте Access-Control-Allow-Origin в Response Headers.
  5. Если перед запросом есть OPTIONS, изучите его статус и заголовки отдельно.
  6. Сравните разрешённые методы, заголовки и credentials с реальным запросом.

Для некоторых cross-origin запросов браузер сначала отправляет preflight методом OPTIONS. Он спрашивает, разрешены ли origin, метод и нестандартные заголовки будущего запроса. Если preflight не прошёл, основной POST или PUT может вообще не уйти. Поэтому смотреть только логи обработчика POST недостаточно.

OPTIONS /api/tasks HTTP/1.1
Origin: http://localhost:5173
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type

Последовательность CORS preflight OPTIONS и основного запроса
Основной запрос уходит только после успешной проверки разрешений, если preflight необходим.

Network разделяет сетевую ошибку и политику браузера

Если запроса нет, ищите ошибку JavaScript или неверный URL. Если OPTIONS возвращает 404 или 500, настройте маршрут preflight. Если ответ есть, но нужного разрешающего заголовка нет, исправление принадлежит серверу или промежуточному прокси.

Какие заголовки должен вернуть сервер

Access-Control-Allow-Origin указывает разрешённый origin. Для credentialed-запросов нельзя сочетать cookies и wildcard *: сервер должен вернуть конкретный origin и Access-Control-Allow-Credentials: true. Разрешённые методы и заголовки должны покрывать фактический запрос, особенно на preflight.

Заголовок ответаНазначениеЧастая ошибка
Access-Control-Allow-OriginКакой origin может читать ответНет точного origin или поставлен список в одной строке
Access-Control-Allow-MethodsДопустимые методыНе указан PUT, PATCH или DELETE
Access-Control-Allow-HeadersДопустимые request headersНе разрешён Content-Type или Authorization
Access-Control-Allow-CredentialsРазрешить cookies и credentialsИспользован wildcard origin
Vary: OriginРазделить кэш по originCDN отдаёт заголовок другого клиента
fetch('https://api.example.test/profile', {
  credentials: 'include',
})

// Сервер должен ответить, например:
// Access-Control-Allow-Origin: https://app.example.test
// Access-Control-Allow-Credentials: true
// Vary: Origin

Где настраивается CORS между фронтендом API и прокси
Разрешение возвращает владелец API или доверенный прокси, а браузер проверяет его.

CORS не является аутентификацией. Разрешающий заголовок сообщает браузеру, можно ли передать ответ JavaScript. Сервер всё равно обязан проверять токены, сессии и права на каждом запросе. Запрет CORS не защищает API от обращений через серверные клиенты, curl или другой backend.

Почему no-cors, расширение и случайный прокси не решают проблему

Режим no-cors создаёт непрозрачный ответ: JavaScript не получает нормальный статус, заголовки и тело. Он подходит для узких сценариев вроде отправки некоторых ресурсов, но не превращает закрытый API в доступный. Расширение, отключающее защиту, меняет только ваш браузер и не исправляет приложение для пользователей.

Добавлять mode no-cors

Ответ становится opaque, и код не сможет прочитать JSON. Проблема лишь меняет форму.

Отключать безопасность браузера

Локальная демонстрация скрывает реальный дефект и создаёт небезопасную среду.

Ставить allow-origin на фронтенде

Заголовок разрешения должен находиться в ответе API, а не в исходящем fetch.

Разрешать любой origin с cookies

Wildcard несовместим с credentialed CORS и создаёт неверную модель доверия.

Рабочее решение воспроизводится в обычном браузере

После настройки откройте приложение в чистом профиле без расширений. В Network должны быть успешный preflight при необходимости и основной ответ с точным origin. В Console не должно оставаться сообщения CORS.

Кто должен исправлять CORS в проекте

Если вы контролируете API, добавьте allowlist origin в backend или API gateway. Если API чужой и не поддерживает браузерные запросы, используйте собственный серверный маршрут, который обращается к нему по официальным условиям. В разработке dev-server proxy может сохранить same-origin для браузера, но продовая архитектура должна иметь такой же понятный маршрут.

Чек-лист исправления
  1. Записаны точные origin фронтенда и API, включая схему и порт.
  2. В Network отдельно проверены OPTIONS и основной запрос.
  3. Сервер возвращает конкретный разрешённый origin.
  4. Метод и пользовательские заголовки разрешены preflight-ответом.
  5. Cookies используются только вместе с credentials и явным origin.
  6. Решение проверено без расширений и отключения безопасности браузера.
Не маскируйте ошибку ответом 200 на OPTIONS без заголовков. Успешный HTTP-статус preflight недостаточен. Браузер сравнивает разрешающие заголовки с будущим запросом. Проверьте их содержимое, а не только зелёный цвет строки в Network.
Короткие ответы
Из чего состоит origin?

Из схемы, хоста и порта. Путь и query-строка в origin не входят.

Почему API вернул 200, а JavaScript не видит ответ?

Браузер может получить сетью ответ, но не открыть его коду из-за отсутствующего или неверного CORS-заголовка.

Что такое preflight?

Предварительный OPTIONS-запрос, которым браузер проверяет разрешение origin, метода и заголовков.

Поможет ли mode no-cors прочитать JSON?

Нет. Ответ станет непрозрачным, и JavaScript не получит обычный доступ к телу.

Можно ли использовать звёздочку вместе с cookies?

Для credentialed CORS сервер должен вернуть конкретный origin, а не wildcard.

Разберите запрос от браузера до API

Потренируйтесь отправлять запросы в курсе JavaScript, затем откройте пошаговый разбор API на JavaScript. Для работы с элементами результата пригодится статья про DOM.

Если запрос запускается по кнопке, проверьте цепочку по материалу о багах JavaScript-кнопок. В Кодике удобно сохранять origin, заголовки и статус каждого шага как короткий воспроизводимый пример.