Ошибка blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present — одна из самых частых проблем при разработке веб-приложений, работающих с внешними API или несколькими доменами. В этой статье разберём, что такое CORS, почему браузер блокирует такие запросы и как правильно настроить заголовки на стороне сервера.
Что такое CORS и почему браузер блокирует запросы
CORS (Cross-Origin Resource Sharing) — механизм безопасности браузера, основанный на HTTP-заголовках. Он ограничивает JavaScript-код на одном домене от обращения к ресурсам другого домена. Это называется политикой одного источника (Same-Origin Policy).
Браузер считает запрос кросс-доменным, если отличается хотя бы один из трёх параметров: протокол, домен или порт. Например, запрос с https://example.com к https://api.other.com — кросс-доменный. Если сервер не сообщает браузеру явно, что разрешает такие запросы, браузер блокирует ответ и выбрасывает ошибку CORS.
Важно понимать: блокировка происходит на стороне браузера, а не сервера. Сервер получает запрос и возвращает ответ, но браузер отказывается передавать этот ответ JavaScript-коду, если в ответе нет нужных заголовков.
Когда возникает эта ошибка
Ошибка появляется при AJAX-запросах (fetch, XMLHttpRequest) к ресурсам на другом домене или поддомене. Типичные сценарии:
- Фронтенд на
localhost:3000обращается к API наlocalhost:8080 - Сайт на
shop.example.comделает запрос кapi.example.com - Веб-приложение обращается к стороннему REST API без CORS-поддержки
- Мобильное веб-приложение запрашивает данные с отдельного бэкенд-сервера
Решение 1: Заголовок в PHP
Самый простой способ — добавить заголовок в PHP-коде. Символ * означает разрешение запросов с любого домена:
|
1 |
header('Access-Control-Allow-Origin: *'); |
Однако в большинстве случаев лучше указывать конкретный домен. Это безопаснее, так как ограничивает круг разрешённых источников:
|
1 2 3 |
header('Access-Control-Allow-Origin: https://your-frontend.com'); header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With'); |
Если нужно разрешить запросы с нескольких доменов, реализуйте динамическую проверку:
|
1 2 3 4 5 |
$allowed = ['https://site1.com', 'https://site2.com']; $origin = $_SERVER['HTTP_ORIGIN'] ?? ''; if (in_array($origin, $allowed)) { header('Access-Control-Allow-Origin: ' . $origin); } |
Решение 2: Настройка через .htaccess (Apache)
Для Apache добавьте в файл .htaccess следующие директивы:
|
1 2 3 |
Header set Access-Control-Allow-Origin "*" Header set Access-Control-Allow-Methods "GET, POST, OPTIONS" Header set Access-Control-Allow-Headers "Content-Type, Authorization" |
Если модуль mod_headers не включён, выполните: a2enmod headers и перезапустите Apache.
Решение 3: Настройка в nginx
В конфигурации nginx добавьте в блок location или server:
|
1 2 3 |
add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always; |
Решение 4: Для Битрикс — где добавить заголовки
В 1С-Битрикс заголовки удобнее всего добавлять в файл /bitrix/php_interface/init.php. Это гарантирует, что заголовки будут установлены при каждом запросе:
|
1 2 3 4 5 6 7 8 9 10 |
// /bitrix/php_interface/init.php if (isset($_SERVER['HTTP_ORIGIN'])) { $allowed = ['https://your-domain.ru']; if (in_array($_SERVER['HTTP_ORIGIN'], $allowed)) { header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']); header('Access-Control-Allow-Credentials: true'); } } header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, X-Requested-With'); |
Обработка preflight OPTIONS-запросов
Браузер перед «сложным» запросом (с нестандартными заголовками или методами PUT/DELETE) автоматически отправляет preflight-запрос методом OPTIONS. Сервер должен корректно на него ответить, иначе основной запрос не выполнится. Добавьте обработчик:
|
1 2 3 4 5 6 7 8 |
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization'); header('Access-Control-Max-Age: 86400'); // кэш preflight на сутки http_response_code(204); exit; } |
Безопасность: почему не стоит всегда использовать *
Символ * удобен для публичных API, но опасен там, где используется авторизация. При Access-Control-Allow-Origin: * нельзя одновременно использовать Access-Control-Allow-Credentials: true — браузер выдаст ошибку. Кроме того, открытый CORS позволяет любому сайту читать ответы вашего API от имени авторизованного пользователя.
Для API с авторизацией всегда указывайте конкретный домен и добавляйте заголовок Vary: Origin, чтобы кэш корректно обрабатывал разные источники:
|
1 2 3 |
header('Access-Control-Allow-Origin: https://trusted-app.com'); header('Access-Control-Allow-Credentials: true'); header('Vary: Origin'); |
Правильная настройка CORS позволяет строить безопасные кросс-доменные интеграции без риска для данных пользователей.
