CORS (Cross-Origin Resource Sharing) — это механизм безопасности браузера, который контролирует доступ к ресурсам с разных источников. Когда фронтенд-приложение пытается получить данные с сервера на другом домене, браузер проверяет специальные заголовки. Если их нет или они настроены неправильно, вы столкнётесь с CORS ошибкой.
Эта проблема встречается постоянно: при работе с API, загрузке шрифтов, изображений или скриптов с третьих сервисов. Понимание причин и методов решения поможет вам быстро диагностировать проблему и выбрать оптимальный способ её устранения.
Почему CORS ошибки вообще существуют
Браузеры блокируют кросс-доменные запросы из соображений безопасности. Без этого механизма любой сайт мог бы украсть данные с другого сайта, используя ваш браузер и вашу сессию. Same Origin Policy — это фундаментальное правило веб-безопасности, которое гласит: скрипты с одного сайта не должны иметь доступ к данным другого сайта.
Одако иногда нужно разрешить кросс-доменный доступ. Для этого существует CORS — набор HTTP заголовков, которые явно говорят браузеру: «Эта услуга разрешает запросы с таких-то доменов». Если сервер не отправляет правильные заголовки или отправляет их неправильно, браузер блокирует запрос. При этом ошибка появляется именно в консоли вашего браузера, но виноват всегда удалённый сервер, который неправильно настроен.
Основные причины CORS ошибок
Почти все CORS проблемы сводятся к нескольким типичным ошибкам конфигурации. Понимание каждой из них поможет вам быстро определить, что именно идёт не так. Давайте разберём самые распространённые сценарии и посмотрим, как они проявляются.
Самая частая ошибка — это отсутствие или неправильная конфигурация заголовка Access-Control-Allow-Origin. Этот заголовок указывает браузеру, с каких доменов разрешены запросы к ресурсам. Если сервер не отправляет этот заголовок вообще, браузер автоматически блокирует запрос. Если заголовок присутствует, но содержит неправильный домен, блокировка тоже произойдёт. Например, если ваше приложение находится на https://myapp.com, а сервер указал Access-Control-Allow-Origin: https://wrongdomain.com, запрос будет заблокирован.
| Причина |
Описание |
Как проявляется |
| Отсутствие Access-Control-Allow-Origin |
Сервер не отправляет заголовок |
Браузер блокирует любой кросс-доменный запрос |
| Неправильный домен в заголовке |
Домен не совпадает с origin запроса |
Ошибка о том, что домен не разрешён |
| Ошибка preflight запроса |
Сервер не обрабатывает OPTIONS запросы |
Запросы с кастомными заголовками не проходят |
| Неправильные методы в Allow-Methods |
Список методов не включает нужный метод |
PUT, DELETE или PATCH запросы отклоняются |
| Отсутствие заголовков в Allow-Headers |
Кастомные заголовки не включены в конфиг |
Запросы с Authorization или X-* заголовками блокируются |
Механизм preflight запросов — ещё один источник проблем. Когда вы отправляете запрос с кастомными заголовками или используете методы вроде PUT или DELETE, браузер сначала отправляет OPTIONS запрос (preflight). Этот запрос — своего рода проверка: «Эй, сервер, ты готов к такому запросу?». Если сервер не обрабатывает OPTIONS или отклоняет его, основной запрос даже не будет отправлен. Часто разработчики забывают настроить обработку preflight запросов, и это приводит к странным ошибкам, когда GET работает, а POST нет.
Ещё одна проблема — несовпадение origins при миграции. Если вы мигрировали приложение с staging окружения на production, URL могли измениться, но конфиг сервера не обновился. В консоли вы увидите сообщение о блокировке, хотя раньше всё работало. Проверьте, совпадают ли текущие URL с теми, что указаны в конфигурации сервера.
Как диагностировать CORS ошибку
Прежде чем искать решение, нужно точно понять, что именно не работает. Правильная диагностика экономит часы времени. Начинайте с самых простых инструментов — они часто дают достаточно информации.
Откройте консоль браузера (нажмите F12 или Ctrl+Shift+I, перейдите на вкладку Console). Там вы увидите сообщение об ошибке типа: Access to fetch at 'https://api.example.com/data' from origin 'https://yoursite.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Это сообщение очень информативно: оно точно указывает, какой домен блокирует запрос и почему.
Перевключитесь на вкладку Network и повторите действие, которое вызывает ошибку. Вы увидите HTTP запрос, который был заблокирован. Кликните на него и посмотрите на вкладку Response Headers. Ищите заголовки вроде Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers. Если они отсутствуют или содержат неправильные значения — вот ваша проблема.
Для более детального анализа используйте специализированные инструменты:
- Chrome DevTools — встроенный инструмент браузера, показывает запросы, заголовки и ошибки
- Postman — позволяет отправлять запросы без браузера и видеть все заголовки
- CORS Helper — расширение для Chrome, которое помогает отладить CORS проблемы
- Fiddler — перехватывает весь сетевой трафик, даёт полный контроль над запросами
Если вы используете инструмент вроде Postman, отправьте тот же запрос с его помощью. Если запрос в Postman проходит успешно, но в браузере заблокирован — это точно CORS. Если запрос не проходит даже в Postman — проблема в самом API или сервере.
Решения: что можно сделать на сервере
Если у вас есть доступ к серверу, который вызывает CORS ошибки, решение простое: настройте правильные заголовки. Это может быть ваш собственный сервер или API, который вы разрабатываете. На стороне сервера всегда можно решить CORS проблему полностью и правильно.
Для Node.js Express приложений самое частое решение — использовать пакет cors или устанавливать заголовки вручную. Вот базовая конфигурация:
const cors = require('cors');
const app = require('express')();
const corsOptions = {
origin: 'https://yoursite.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
maxAge: 86400
};
app.use(cors(corsOptions));
Эта конфигурация говорит серверу: разреши запросы только с https://yoursite.com, для методов GET, POST, PUT и DELETE, с заголовками Content-Type и Authorization, с поддержкой credentials (куки, авторизация) и кешируй эту информацию на 24 часа.
Если нужно разрешить несколько доменов, используйте динамическую проверку:
const allowedOrigins = [
'https://yoursite.com',
'https://app.yoursite.com',
'http://localhost:3000'
];
const corsOptions = {
origin: function(origin, callback) {
if (allowedOrigins.indexOf(origin) !== -1 || !origin) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
};
app.use(cors(corsOptions));
Важный момент с preflight запросами: если вы устанавливаете заголовки вручную (без пакета cors), обязательно обработайте OPTIONS запросы:
app.options('*', (req, res) => {
res.setHeader('Access-Control-Allow-Origin', 'https://yoursite.com');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Request-ID');
res.setHeader('Access-Control-Max-Age', '86400');
res.status(204).end();
});
Эта функция обрабатывает preflight запросы для всех маршрутов (*). Браузер отправляет OPTIONS запрос перед реальным запросом, и если сервер не ответит правильно, реальный запрос не будет отправлен.
Правильный порядок middleware в Express критически важен: middleware CORS должна запуститься до ваших маршрутов:
app.use(cors(corsOptions)); // CORS middleware первой
app.use('/api', apiRoutes); // Потом маршруты
app.use(errorHandler); // И обработчик ошибок последним
Если вы поставите CORS после маршрутов, она не будет работать. Если вы забудете про неё вообще — CORS ошибка гарантирована.
Для других языков и фреймворков принцип одинаковый:
- Python Flask: используйте расширение
flask-cors
- Django: установите
django-cors-headers
- Java Spring: настройте
WebMvcConfigurer
- PHP: установите заголовки вручную через
header()
Клиентские обходы и ограничения
Если у вас нет доступа к серверу, который вызывает CORS ошибку, можно попробовать несколько обходных путей. Однако учтите: это не полноценные решения, а скорее временные меры или специальные техники для конкретных сценариев.
CORS Proxy — один из классических обходов. Суть простая: вместо запроса напрямую на внешний сервер, вы запрашиваете через промежуточный сервис, который добавляет нужные CORS заголовки. Например, вместо запроса на https://api.example.com/data вы запрашиваете https://corsproxy.io/?url=https://api.example.com/data. Proxy сервис запрашивает данные с оригинального источника и возвращает их вам с правильными CORS заголовками. Минусы: зависимость от третьего сервиса, потенциальные проблемы с приватностью, если сервис логирует запросы.
Простые запросы — другой подход. Браузер не требует preflight для простых запросов. Простой запрос — это GET или POST без кастомных заголовков, с Content-Type: application/x-www-form-urlencoded, multipart/form-data или text/plain. Если переделать ваш запрос в простой, preflight не будет отправляться. Но это работает только если сервер всё равно отправляет Access-Control-Allow-Origin заголовок.
JSONP — очень старый метод, который работает, потому что не использует fetch API. Вместо этого скрипт динамически загружается как <script> тег, а результат оборачивается в функцию обратного вызова. Минусы: очень старый метод, работает только с GET запросами, сервер должен специально поддерживать JSONP формат.
Server-Side Rendering (SSR) или Backend Proxy — если вы контролируете свой бекенд, сделайте запрос к внешнему API на сервере, а не на клиенте. Так CORS не будет проблемой, потому что server-to-server коммуникация не подвергается Same Origin Policy.
Таблица с сравнением подходов:
| Подход |
Плюсы |
Минусы |
| CORS Proxy |
Быстро реализовать |
Зависимость от третьего сервиса, риск приватности |
| Простые запросы |
Работает без preflight |
Ограниченная функциональность, нужна поддержка сервера |
| JSONP |
Очень старый метод, совместимость |
Только GET, сложная отладка, безопасность |
| SSR/Backend Proxy |
Безопасно, полный контроль |
Требует изменения архитектуры приложения |
Важно понимать: клиентские обходы — не решение CORS проблемы, а лишь временные меры. Правильное решение всегда на стороне сервера, который неправильно настроен.
Специальные случаи и подводные камни
Бывают ситуации, когда CORS ошибка возникает из-за чего-то неожиданного. Эти случаи часто вводят в заблуждение, потому что стандартные решения не помогают. Давайте разберём некоторые типичные “подводные камни”.
Wildcard с credentials — частая ошибка. Если вы устанавливаете Access-Control-Allow-Origin: * (разрешить все домены), то одновременно нельзя использовать Access-Control-Allow-Credentials: true. Это ограничение безопасности: если разрешить все домены и одновременно отправлять куки/авторизацию, любой сайт сможет использовать вашу сессию. Браузер просто блокирует такую комбинацию.
Кеширование и старые правила — если сервер неправильно настроил CORS, а потом вы исправили конфиг, браузер может использовать закешированные правила из первого запроса. Вкладка Network показывает заголовок Access-Control-Max-Age, который указывает, сколько секунд браузер помнит эти правила (обычно 86400 секунд = 24 часа). Чтобы очистить кеш, сделайте Hard Refresh (Ctrl+Shift+R).
Subscription Keys в заголовках — если вы используете Azure API Management или похожий сервис с ключами аутентификации, отправлять ключ в заголовке может быть проблематично. CORS конфиг может не пропускать кастомные заголовки. Решение: отправьте ключ как query параметр (?subscription-key=xyz) вместо заголовка.
Разные origins в development и production — если вы разрабатываете локально (http://localhost:3000), а deploy идёт на https://app.yoursite.com, конфиг CORS должен включать оба. Часто разработчики забывают добавить production домен в список разрешённых и потом удивляются, почему на production CORS ошибка.
Ошибки в обработке OPTIONS запросов — если ваша обработчик ошибок запущен до обработки CORS, OPTIONS запрос может быть отклонён. Убедитесь, что CORS middleware запущена в правильном порядке и не пересекается с обработчиком ошибок.
Редиректы с CORS — если API возвращает редирект (статус 301 или 302), браузер может отклонить редирект при кросс-доменном запросе. Это зависит от конкретного случая и конфигурации, но обычно требует явного разрешения в CORS политике.
На что стоит обратить внимание
Когда вы работаете с CORS, помните несколько ключевых принципов. Во-первых, CORS существует для вашей безопасности — это не баг и не раздражающий механизм, а необходимая защита. Не пытайтесь её полностью отключать через wildcard *, если у вас есть конфиденциальные данные.
Во-вторых, всегда проверяйте именно server-side конфиг. Браузер просто выполняет правила безопасности; если что-то не работает, проблема в том, как настроен удалённый сервер. Никакие трюки на клиенте не помогут, если сервер не отправляет правильные заголовки.
В-третьих, тестируйте в разных браузерах. Некоторые браузеры могут быть мягче в интерпретации CORS правил, но это редко. Обычно все браузеры ведут себя одинаково. Если CORS не работает в Chrome, не будет работать и в Firefox.