Веб

Ошибка 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?

Вот мой код запроса:

javascript
$.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

Представьте: ваш 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

Взглянем на код:

javascript
$.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 вручную. Нет изменений? Сервер виноват.


Источники

  1. MDN: CORSMissingAllowOrigin
  2. MDN: Access-Control-Allow-Origin
  3. Postman: Fixing a CORS error
  4. Postman Blog: What Is CORS?
  5. Sentry: No Access-Control-Allow-Origin
  6. Stack Overflow: CORS in browser vs Postman
  7. Stack Overflow: Preflight issue
  8. Learn JavaScript: Fetch crossOrigin
  9. TProger: CORS от А до Я
  10. OpenReplay: Понимание CORS

Заключение

Ошибка Access-Control-Allow-Origin — это браузерная защита от нежелательных кросс-доменов, которой нет в Postman. Понимая SOP и preflight, вы видите: проблема не в вашем коде, а в отсутствии серверных заголовков. Тестируйте в браузере с самого начала, чтобы избежать сюрпризов в проде. Если Flask — подумайте о flask-cors, но главное: безопасность превыше удобства.

Авторы
Проверено модерацией
Модерация
Ошибка Access-Control-Allow-Origin: браузер vs Postman