Как исправить ошибки CORS (и почему они возникают)
Ошибка CORS означает, что ваша веб-страница пыталась прочитать ответ от другого источника (другого домена, порта или протокола), а сервер этого источника не прислал заголовки, которые разрешают доступ. Браузер блокирует ответ, чтобы защитить пользователей. Исправляется это на сервере: нужно вернуть заголовок Access-Control-Allow-Origin для origin вашего сайта и обработать preflight-запросы OPTIONS. Либо можно обойти проблему, отправляя запрос через собственный бэкенд на том же origin.
Это одна из самых запутанных ошибок в веб-разработке, потому что запрос часто доходит до сервера нормально. В этом руководстве объясняется, что происходит, и как исправить каждый вариант.
Что такое «origin»
Origin — это сочетание протокола, домена и порта:
| URL | Origin |
|---|---|
https://myapp.com/dashboard | https://myapp.com |
https://api.myapp.com/users | https://api.myapp.com (другой поддомен, поэтому другой origin) |
http://localhost:5173 | http://localhost:5173 |
http://localhost:3000 | http://localhost:3000 (другой порт, поэтому другой origin) |
Браузеры следуют политике одного источника (same-origin policy): JavaScript на странице свободно читает ответы со своего origin, но не с чужих. CORS (Cross-Origin Resource Sharing) — это механизм, с помощью которого сервер сознательно разрешает доступ: «ответы от меня могут читать страницы с этих origin».
Два следствия часто удивляют:
- Запрос часто доходит до сервера. CORS обычно блокирует чтение ответа вашим кодом, а не получение запроса сервером. POST мог быть сохранён, даже если вы увидели ошибку.
- Ограничение действует только в браузерах. curl, Postman и серверный код CORS не применяют. Поэтому «работает в Postman» ничего не говорит о CORS.
Шаг 1: Прочитайте точный текст ошибки
Откройте инструменты разработчика (правый клик → Inspect / «Просмотреть код»), перейдите на вкладку Console и прочитайте полное сообщение. Формулировка Chrome подскажет, в чём проблема:
| Сообщение содержит | Что не так |
|---|---|
No 'Access-Control-Allow-Origin' header is present | Сервер вообще не отправил заголовок, либо запрос завершился до вашего CORS-кода (например, 404 или 500) |
The 'Access-Control-Allow-Origin' header has a value '...' that is not equal to the supplied origin | Сервер разрешает другой origin, не ваш |
Response to preflight request doesn't pass access control check | Preflight-запрос OPTIONS не прошёл проверку |
...must not be the wildcard '*' when the request's credentials mode is 'include' | Вы отправляете cookie, а сервер ответил * |
Request header field authorization is not allowed | Сервер не перечислил этот заголовок в Access-Control-Allow-Headers |
Затем откройте вкладку Network, найдите неудачный запрос (и предшествующий ему запрос OPTIONS) и посмотрите код статуса и заголовки ответа.
Сначала проверьте код статуса. Многие «ошибки CORS» на самом деле являются серверными ошибками в маскировке. Если сервер падает или возвращает 404, его ответ часто не содержит CORS-заголовков, и браузер сообщает о CORS вместо настоящей проблемы. Исправьте ошибку 500 или 404, и ошибка CORS может исчезнуть.
Шаг 2: Решите, нужно ли вообще вызывать этот API из браузера
Прежде чем добавлять заголовки, спросите себя: должен ли браузер обращаться к этому API напрямую?
- Сторонний API, которому нужен секретный ключ (OpenAI, секретный ключ Stripe, большинство почтовых сервисов), никогда не должен вызываться из браузера. Ключ станет видимым каждому посетителю. Вызывайте такой API из бэкенда — и проблема CORS исчезнет, потому что запросы от сервера к серверу на CORS не распространяются. См. как держать ключи API в безопасности.
- Сторонний API, которым вы не управляете, и который не отправляет CORS-заголовки, нельзя исправить с вашей стороны. Вызывайте его из бэкенда.
- Ваш собственный API можно настроить правильно. Продолжайте ниже.
Шаг 3: Исправьте сервер
Сервер должен добавлять правильные заголовки в свои ответы, включая ответы с ошибками.
Для самого запроса отправьте:
Access-Control-Allow-Origin: https://myapp.com(точный origin вашего фронтенда, без слеша в конце)Vary: Origin, если значение зависит от того, какой origin сделал запрос
Для preflight-запроса OPTIONS ответьте статусом успеха (200 или 204) и добавьте:
Access-Control-Allow-Origin: https://myapp.comAccess-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETEAccess-Control-Allow-Headers: Content-Type, Authorization(каждый собственный заголовок, который вы отправляете)- По желанию
Access-Control-Max-Age: 600, чтобы браузер некоторое время кэшировал preflight
У большинства фреймворков есть пакет или настройка, которая делает это за вас. В Express — пакет cors, в FastAPI — CORSMiddleware, в ASP.NET Core — AddCors, в Spring — @CrossOrigin или конфигурация CORS. Типичная настройка Express:
import cors from "cors";
app.use(cors({
origin: ["https://myapp.com", "http://localhost:5173"],
credentials: true,
}));
Обработку CORS ставьте до маршрутов и аутентификации, чтобы preflight-запросы (которые не несут данных для входа) не отклонялись с ошибкой 401.
С AI-конструктором опишите настройку точно:
Фронтенд на
https://myapp.comобращается к API наhttps://api.myapp.comи получает ошибку «Response to preflight request doesn't pass access control check». Настрой CORS на API так, чтобы разрешался ровноhttps://myapp.comи origin предпросмотра, разреши методы GET, POST, PATCH, DELETE с заголовками Content-Type и Authorization, обрабатывай OPTIONS до аутентификации и добавляй CORS-заголовки также в ответы с ошибками.
Шаг 4: Настройте cookie и учётные данные
Если ваш фронтенд отправляет на другой origin cookie (например, сессию входа), обе стороны должны быть согласованы:
- Фронтенд ставит
credentials: "include"вfetchилиwithCredentials: trueв axios. - Сервер отправляет
Access-Control-Allow-Credentials: true. Access-Control-Allow-Originна сервере должен быть точным origin, а не*.
Межсайтовым cookie также нужны SameSite=None; Secure, а некоторые браузеры дополнительно ограничивают сторонние cookie. Это ещё одна причина предпочесть подход из шага 5.
Шаг 5: Избегайте CORS с помощью настройки same-origin
Часто самое чистое решение — вообще не делать кросс-доменных запросов. Раздавайте API с того же origin, что и страницу, например по адресу https://myapp.com/api/...:
- В разработке большинство dev-серверов умеют проксировать запросы. Настройка
server.proxyв Vite может перенаправлять/apiна ваш бэкенд на другом порту, так что браузер общается только с одним origin. - В продакшене направляйте
/apiна бэкенд через настройки rewrite или прокси вашего хостинга, либо раздавайте фронтенд и API с одного сервера.
Тогда фронтенд обращается к относительному пути вроде fetch("/api/orders"). Нет кросс-доменного запроса, нет preflight между origin и проблем с cookie. Также не нужно жёстко прописывать домен API, который различается между предпросмотром и продакшеном.
Переделай фронтенд так, чтобы он обращался к бэкенду по относительным путям /api, а не по полному URL, и настрой прокси для разработки, чтобы /api перенаправлялся на сервер API. Затем удали настройки CORS, которые больше не нужны.
Чего не делать
mode: "no-cors"вfetch. Это ничего не обходит: ваш код получит пустой, нечитаемый ответ.- Расширения браузера, которые отключают CORS. Они меняют только ваш браузер. Каждый посетитель всё равно увидит ошибку.
- Публичные «CORS-прокси» для реальных приложений. Они могут читать и менять всё, что через них проходит, и периодически перестают работать.
Access-Control-Allow-Origin: *для API с логинами. С учётными данными это всё равно не работает, и это неправильное значение по умолчанию для закрытых данных.- Отражение любого заголовка
Originобратно с разрешёнными учётными данными. Так любой сайт может делать запросы от имени залогиненных пользователей. Используйте список разрешённых origin.
Частые причины в приложениях, созданных с ИИ
| Ситуация | Решение |
|---|---|
| Фронтенд вызывает сторонний API с секретным ключом | Перенесите вызов на бэкенд |
| Работает в предпросмотре, не работает на опубликованном домене | Добавьте опубликованный домен и свой домен в список разрешённых или используйте относительные пути /api |
| Ошибка только на POST, PUT или DELETE | Preflight не обработан; добавьте обработку OPTIONS и разрешённые заголовки |
| Ошибка появилась после добавления входа | Учётным данным нужен точный origin и Allow-Credentials |
| «Ошибка CORS», но во вкладке Network статус 500 | Сначала исправьте серверную ошибку |
URL API жёстко прописан как localhost | Используйте относительные пути или настройку для каждого окружения |
CORS в Mythex
Когда в проекте Mythex есть и сайт, и сервер API, оба работают как сервисы в одном проекте, и между ними можно переключаться в предпросмотре. Если попросить агента сделать так, чтобы фронтенд обращался к бэкенду по относительному пути /api с проксированием в разработке, большинство проблем с CORS исчезает. Если всё же нужны кросс-доменные вызовы, передайте агенту точный текст ошибки из консоли и участвующие origin. Проверьте результат и на опубликованном приложении, поскольку его адрес отличается от адреса предпросмотра. Подробнее см. Multi-service apps и API backends в документации, а также наши руководства: как отлаживать приложение, созданное с ИИ и фронтенд и бэкенд.
Чек-лист по CORS
- Прочитаны полное сообщение консоли и код статуса неудачного запроса
- Проверено, нет ли скрытой ошибки 404 или 500
- API с секретными ключами вызывается из бэкенда, а не из браузера
- Сервер отправляет
Access-Control-Allow-Originс точными разрешёнными origin - Preflight
OPTIONSотвечает разрешёнными методами и заголовками, до аутентификации - CORS-заголовки есть и в ответах с ошибками
- Учётные данные: точный origin,
Allow-Credentials: true, правильные настройки cookie - Учтены origin предпросмотра, опубликованного сайта и собственного домена, либо используются относительные пути
/api - Нет обходных путей через
no-cors, публичные прокси или отражение любого origin
Вопросы
Что означает ошибка CORS?
Ваша страница, загруженная с одного источника, попыталась прочитать ответ с другого, а сервер того источника не отправил заголовки, которые разрешают такой доступ. Браузер не даёт вашему коду прочитать ответ. Исправляется это на сервере или в маршрутизации запроса, а не во фронтенд-коде.
Можно ли исправить ошибку CORS на фронтенде?
Нормально — нельзя. Настройки вроде mode: 'no-cors' только скрывают ответ от вашего кода. Исправить можно, заставив сервер отправлять правильный заголовок Access-Control-Allow-Origin, либо вызывая API из собственного бэкенда или прокси на том же origin.
Безопасен ли Access-Control-Allow-Origin: *?
Для публичных данных, которые может читать любой, например для открытого API без входа, это нормально. Такой заголовок нельзя использовать с cookie и другими учётными данными, а для закрытых API стоит перечислять точные разрешённые origin.
Почему запрос работает в Postman или curl, но не в браузере?
CORS применяется только браузерами. Postman и curl его не проверяют, поэтому запрос может пройти там, но быть заблокирован, когда его отправляет JavaScript страницы.
Что такое preflight-запрос?
Для непростых запросов, например JSON POST или запроса с заголовком Authorization, браузер сначала отправляет запрос OPTIONS и спрашивает сервер, какие методы и заголовки разрешены. Если этот запрос OPTIONS не проходит или в нём нет нужных заголовков, основной запрос не отправляется.