CORS: что это такое, почему блокирует запросы и как это исправить
Если вы хоть раз писали фронтенд, который обращается к стороннему API, - вы наверняка видели в консоли эту красную ошибку:
Access to fetch at 'https://api.example.com' from origin '
http://localhost:3000' has been blocked by CORS policy
Статья объясняет, что такое CORS, зачем браузер его проверяет, почему возникают ошибки и как их правильно решать - без костылей и «просто отключить».
Подойдёт разработчикам, которые только начали работать с API, и тем, кто уже сталкивался с CORS, но до конца не разобрался.
Что такое CORS
CORS (Cross-Origin Resource Sharing) - механизм, который контролирует, с каких доменов браузер может делать HTTP-запросы к серверу.
По умолчанию браузер разрешает делать запросы только к тому же источнику (origin), с которого загружена страница. Это называется политика одного источника (Same-Origin Policy).
Источник - это комбинация из трёх частей:
протокол (http / https)
домен (example.com)
порт (:3000, :443)
Если хоть одна часть отличается - это уже кросс-доменный запрос, и браузер применяет CORS-проверку.
Источник страницы | Запрос к | CORS нужен? |
|---|---|---|
Нет | ||
Да | ||
Да | ||
Да |
Зачем нужен CORS и почему его нельзя просто отключить
CORS - это защита от атак типа CSRF (Cross-Site Request Forgery). Без него любой сайт мог бы от вашего имени делать запросы к вашему банку, почте или другим сервисам - ведь браузер автоматически отправляет куки сессии.
Важный нюанс: CORS - это ограничение браузера, а не сервера. Сервер получает запрос в любом случае. Браузер лишь решает, отдавать ли ответ клиентскому коду.
Именно поэтому «отключить CORS» через расширение браузера работает только у вас локально - в продакшене это ничего не изменит.
Как работает CORS: preflight и простые запросы
Браузер делит кросс-доменные запросы на два типа.
Простые запросы (Simple Requests)
Выполняются напрямую, если одновременно выполнены условия:
метод: GET, POST или HEAD
заголовки только стандартные: Accept, Content-Type (только text/plain, multipart/form-data, application/x-www-form-urlencoded)
Браузер добавляет заголовок Origin и проверяет ответ сервера.
Предварительный запрос (Preflight)
Для всего остального (PUT, DELETE, PATCH, произвольные заголовки, application/json) браузер сначала отправляет OPTIONS-запрос - «спрашивает» сервер: можно ли?
OPTIONS /api/data HTTP/1.1
Origin: https://myapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, AuthorizationСервер должен ответить заголовками, которые разрешают запрос:
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400Только после успешного preflight браузер отправит реальный запрос.
Частые ошибки CORS и их причины
1. Сервер вообще не отдаёт CORS-заголовки
Самая частая ситуация. Бэкенд написан, но разработчик не добавил заголовки. Браузер блокирует ответ.
2. Access-Control-Allow-Origin: * с куками
Если запрос отправляется с credentials: 'include' (куки, HTTP-авторизация), сервер не может использовать *. Нужно указать конкретный origin.
// Ошибка
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true // ← это сочетание невалидно3. Разный регистр или слеш в конце домена
https://site.com и https://site.com/ - разные origins для браузера.
4. Preflight падает с 404 или 405
Сервер не обрабатывает OPTIONS-запросы. Фреймворки вроде Express не делают это автоматически.
5. Разработка на localhost с HTTPS-бэкендом
Смешение протоколов (http://localhost → https://api.example.com) - кросс-доменный запрос.
Пошаговое решение: как настроить CORS на сервере
Node.js / Express
const cors = require('cors');
// Разрешить всем (только для разработки!)
app.use(cors());
// Конкретный origin
app.use(cors({
origin: 'https://myapp.com',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true,
}));
// Динамический список origins
const allowedOrigins = ['https://myapp.com', 'https://staging.myapp.com'];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
}));Nginx
location /api/ {
add_header Access-Control-Allow-Origin "https://myapp.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
add_header Access-Control-Allow-Credentials "true" always;
if ($request_method = OPTIONS) {
return 204;
}
}Python / Django
# settings.py
INSTALLED_APPS = [..., 'corsheaders']
MIDDLEWARE = ['corsheaders.middleware.CorsMiddleware', ...]
CORS_ALLOWED_ORIGINS = [
"https://myapp.com",
]
CORS_ALLOW_CREDENTIALS = TruePHP
header("Access-Control-Allow-Origin: https://myapp.com");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit();
}Примеры из реальных сценариев
React-приложение обращается к стороннему API
Если API не ваш и не поддерживает CORS - настройте прокси на своём сервере:
// vite.config.js / webpack devServer
server: {
proxy: {
'/api': {
target: 'https://third-party-api.com',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}Ваш фронтенд будет обращаться к /api/..., а Vite перенаправит запрос на сторонний сервер. Браузер видит только ваш домен - CORS не срабатывает.
Postman работает, браузер - нет
Это классика. Postman не браузер, он не применяет CORS. Если запрос проходит в Postman, но не в браузере - проблема именно в CORS-заголовках сервера, а не в запросе.
Часто задаваемые вопросы (FAQ)
Почему CORS проверяет браузер, а не сервер? Потому что CORS - это механизм безопасности браузера. Сервер не знает, откуда пришёл запрос (от браузера пользователя или от curl). Браузер же защищает пользователя от того, чтобы сторонние сайты не делали запросы от его имени.
Можно ли обойти CORS без изменения бэкенда? Только в разработке: через прокси в Vite/webpack или расширение для браузера. В продакшене - нет. Нужно настраивать сервер.
Что делать, если я не контролирую бэкенд? Настройте прокси на своём сервере (Node.js, Nginx), который будет проксировать запросы к стороннему API. Фронтенд будет обращаться к вашему серверу.
Почему Access-Control-Allow-Origin: * не работает с куками? Это намеренное ограничение стандарта. Если сервер разрешает все origins и при этом принимает credentials, это дыра в безопасности. Нужно явно указать конкретный origin.
Что такое Access-Control-Max-Age? Это время в секундах, на которое браузер кешируют результат preflight-запроса. Если поставить 86400 (сутки), браузер не будет делать OPTIONS перед каждым запросом.
Влияет ли CORS на SEO? Нет. Поисковые боты не браузеры, они не применяют CORS. CORS - исключительно браузерный механизм.
CORS и HTTPS - одно и то же? Нет. HTTPS - шифрование трафика. CORS - контроль кросс-доменных запросов. Это разные вещи, хотя оба влияют на безопасность.
Почему OPTIONS-запрос возвращает 404? Сервер не зарегистрировал обработчик для метода OPTIONS. В Express нужно явно добавить app.options('*', cors()) или использовать middleware до маршрутов.
Полезные советы и лучшие практики
Никогда не используйте * в продакшене с credentials: true - это не работает и небезопасно.
Кешируйте preflight через Access-Control-Max-Age - уменьшит число OPTIONS-запросов.
Используйте конкретный список origins вместо wildcard там, где знаете домены заранее.
Проверяйте CORS через DevTools (вкладка Network → запрос → заголовки Response). Ошибка в консоли часто неинформативна, а заголовки - нет.
Не путайте CORS и аутентификацию - CORS разрешает запрос, но не авторизует пользователя. Это разные слои.
Логируйте заблокированные origins на сервере - поможет при отладке.
В Docker убедитесь, что контейнеры ссылаются друг на друга по именам сервисов, а не localhost.
Итог
CORS - не баг и не прихоть разработчиков браузеров. Это защита ваших пользователей от атак через кросс-доменные запросы.
Если вы видите ошибку CORS:
Откройте DevTools → Network → найдите упавший запрос → посмотрите заголовки ответа.
Убедитесь, что сервер возвращает Access-Control-Allow-Origin с правильным значением.
Если запрос с credentials, используйте конкретный origin, а не *.
Если это preflight - убедитесь, что сервер обрабатывает OPTIONS.
Если бэкенд не ваш - настройте прокси.
Понимание CORS избавит вас от часов отладки и поможет не создавать дыры в безопасности.
Комментарии
Чтобы оставить комментарий, войдите в аккаунт.