Ошибка Access-Control-Allow-Origin: браузер vs Postman
Почему возникает ошибка 'На запрашиваемом ресурсе отсутствует заголовок Access-Control-Allow-Origin' в браузере при JS-запросах к API, но работает в Postman. Объяснение CORS, SOP, preflight и отличий клиентов. Анализ с Flask и jQuery.ajax.
Почему я получаю ошибку “На запрашиваемом ресурсе отсутствует заголовок ‘Access-Control-Allow-Origin’” при выполнении JavaScript-запросов к REST API, в то время как тот же запрос работает корректно в Postman?
Я пытаюсь реализовать авторизацию в JavaScript, подключаясь к REST API, созданному с помощью Flask. При выполнении запроса с помощью XMLHttpRequest или fetch я получаю следующую ошибку:
XMLHttpRequest не может загрузить http://myApiUrl/login.
На запрашиваемом ресурсе отсутствует заголовок 'Access-Control-Allow-Origin'.
Поэтому доступ с источника 'null' не разрешен.
Я понимаю, что API должен включать соответствующие заголовки CORS, но почему эта ошибка возникает в браузере при выполнении прямых JavaScript-запросов, но не при использовании инструментов вроде Postman?
Вот мой код запроса:
$.ajax({
type: 'POST',
dataType: 'text',
url: api,
username: 'user',
password: 'pass',
crossDomain: true,
xhrFields: {
withCredentials: true,
},
})
.done(function (data) {
console.log('done');
})
.fail(function (xhr, textStatus, errorThrown) {
alert(xhr.responseText);
alert(textStatus);
});
Этот вопрос конкретно касается понимания того, почему браузеры применяют ограничения CORS при выполнении JavaScript-запросов, в то время как инструменты вроде Postman не имеют таких ограничений, а не о том, как настроить CORS на сервере или обойти эту ошибку.
Ошибка “На запрашиваемом ресурсе отсутствует заголовок ‘Access-Control-Allow-Origin’” — классическая проблема CORS, когда браузер блокирует JavaScript-запрос к API на другом домене без разрешения от сервера. Postman обходит это, потому что не следует браузерным правилам безопасности Same-Origin Policy (SOP). В вашем случае с Flask API и кодом на jQuery.ajax запрос падает из-за отсутствия CORS-заголовков в ответе, особенно с withCredentials: true.
Содержание
- Что вызывает ошибку Access-Control-Allow-Origin
- Почему браузер блокирует запросы, а Postman — нет
- Same-Origin Policy: основа CORS
- Preflight-запросы и OPTIONS
- Анализ вашего кода и с withCredentials
- Как быстро проверить заголовки
- Источники
- Заключение
Что вызывает ошибку Access-Control-Allow-Origin
Представьте: ваш JavaScript на локальной странице (origin ‘null’ или localhost:3000) пытается POST’ить на http://myApiUrl/login. Браузер видит разницу доменов — и стоп. Без заголовка Access-Control-Allow-Origin в ответе сервера он просто не пропустит данные. Это не баг Flask, а защита.
Согласно официальной документации MDN, браузер требует этот заголовок, чтобы подтвердить: “Да, этот сайт может читать мой ответ”. Если его нет — ошибка CORS access control allow origin в консоли. А Postman? Он как curl: шлёт запрос напрямую, без оглядки на origin вашего сайта.
Почему именно такая формулировка ошибки? Браузеры (Chrome, Firefox) проверяют ответ на наличие Access-Control-Allow-Origin: * или конкретного вашего origin. Нет — и XMLHttpRequest/fetch молча фейлятся, даже если сервер вернул 200 OK.
Почему браузер блокирует запросы, а Postman — нет
Вот в чём соль. Postman — это десктопное приложение, не браузер. Оно игнорирует CORS полностью. Ваш запрос в нём улетает “голым”, сервер отвечает — и вуаля, данные в ответе.
Поддержка Postman прямо говорит: “CORS-ошибки возникают из-за ограничений браузеров на кросс-доменный обмен ресурсами”. В их блоге добавляют: без браузерного контекста (origin страницы) нет нужды в заголовках Access-Control-Allow-Origin.
А браузер? Он enforcer’ит SOP. Если скрипт с example.com стучится на api.com — preflight или simple check. Postman такого не делает. Тестируете API? Postman/Insomnia/curl — ок. Frontend? Готовьтесь к бою с CORS.
Коротко: ошибка cors в браузере — не про сервер, а про клиентскую политику. Сервер может быть идеален, но без заголовков браузер скажет “нет”.
Same-Origin Policy: основа CORS
Same-Origin Policy — это фундамент. Браузеры по умолчанию запрещают чтение ответа с другого origin (протокол+домен+порт). Ваш ‘null’ (file:// или пустой) vs http://myApiUrl — разные origins.
CORS (Cross-Origin Resource Sharing) — механизм обхода. Сервер добавляет заголовки:
Access-Control-Allow-Origin: http://your-site.com(или*)Vary: Originдля динамики.
Без них — ошибка именно такая, как в вашем XMLHttpRequest. Sentry объясняет просто: CORS клиентский, не серверный барьер.
Интересно, почему ‘null’? Локальный файл или iframe без src. Но суть одна: браузер guards’ит.
Preflight-запросы и OPTIONS
Ваш POST с withCredentials: true и custom заголовками (jQuery добавляет X-Requested-With) — не “simple request”. Браузер шлёт OPTIONS preflight заранее.
Сервер должен ответить на OPTIONS:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Stack Overflow подтверждает: Postman не шлёт preflight, браузер — шлёт. Если OPTIONS фейлится без заголовков — основной запрос даже не уйдёт.
На русском LearnJS: сервер проверяет Origin и эхом возвращает его в Allow-Origin. Нет — блок.
В консоли Network увидите OPTIONS 403/405? Вот и причина.
Анализ вашего кода и с withCredentials
Взглянем на код:
$.ajax({
type: 'POST',
dataType: 'text',
url: api,
username: 'user',
password: 'pass',
crossDomain: true,
xhrFields: { withCredentials: true },
})
crossDomain: true намекает jQuery на CORS. Но withCredentials: true — killer. Оно требует Access-Control-Allow-Credentials: true на сервере + конкретный origin (не *!).
MDN предупреждает: с credentials * запрещён. Flask без flask-cors pip’а не добавит заголовки автоматически.
Postman без credentials проходит. В браузере — двойной удар: preflight + credentials.
Решение? Не в обходе (не просите отключать CORS!), а в сервере. Но вопрос не об этом.
Как быстро проверить заголовки
Откройте DevTools > Network. Сделайте запрос — увидите failed OPTIONS или основной без CORS headers.
Имитируйте curl:
curl -H "Origin: http://localhost" -X OPTIONS http://myApiUrl/login -v
Нет Allow-Origin? Всё ясно.
OpenReplay советует: Postman не применяет CORS, браузеры — да. TProger добавляет: редиректы тоже ломают без заголовков.
Шаг 1: проверьте ответ сервера в Postman (Headers tab). Шаг 2: добавьте Origin вручную. Нет изменений? Сервер виноват.
Источники
- MDN: CORSMissingAllowOrigin
- MDN: Access-Control-Allow-Origin
- Postman: Fixing a CORS error
- Postman Blog: What Is CORS?
- Sentry: No Access-Control-Allow-Origin
- Stack Overflow: CORS in browser vs Postman
- Stack Overflow: Preflight issue
- Learn JavaScript: Fetch crossOrigin
- TProger: CORS от А до Я
- OpenReplay: Понимание CORS
Заключение
Ошибка Access-Control-Allow-Origin — это браузерная защита от нежелательных кросс-доменов, которой нет в Postman. Понимая SOP и preflight, вы видите: проблема не в вашем коде, а в отсутствии серверных заголовков. Тестируйте в браузере с самого начала, чтобы избежать сюрпризов в проде. Если Flask — подумайте о flask-cors, но главное: безопасность превыше удобства.