Загрузка...
Загрузка...
Нашли баг, сбой или ошибку в работе reChecker? Есть предложение по улучшению? Напишите нам, чтобы мы проверили и исправили проблему в сервисе.
Разбираем CORS простыми словами: preflight-запросы, заголовки Access-Control-*, типичные ошибки настройки на бэкенде и примеры конфигурации для Nginx и Express.
Быстрые определения и связанные понятия из SEO-глоссария reChecker.
CORS (Cross-Origin Resource Sharing) — механизм браузера для контроля запросов между разными доменами. Неправильная настройка CORS — угроза безопасности.
Читать в глоссарии →Cookie (куки) — небольшие файлы данных, сохраняемые браузером. Используются для авторизации, аналитики и персонализации. GDPR требует согласия на аналитические куки.
Читать в глоссарии →Минификация (minification) — удаление лишних символов из CSS, JS и HTML без изменения функциональности. Уменьшает размер файлов на 20-60%.
Читать в глоссарии →CDN (Content Delivery Network) — сеть серверов по всему миру для быстрой доставки контента пользователям. Снижает TTFB и ускоряет загрузку статики.
Читать в глоссарии →Практическое руководство по реализации аутентификации с JWT: выдача токенов, проверка на сервере, хранение на клиенте и обновление сессии.
РазработкаПодробное руководство по JWT: структура токена, заголовок, payload, подпись, алгоритмы и рекомендации по безопасному использованию.
РазработкаКак форматировать SQL запросы, зачем нужен SQL Formatter, правила написания читаемого SQL и онлайн-инструменты.
РазработкаКак форматировать XML онлайн, что такое XML, как устроены RSS, Sitemap и SOAP. Ошибки XML и как их исправить.
Поделитесь с коллегами или изучите другие материалы блога
«Access to fetch at ... has been blocked by CORS policy» — пожалуй, одна из самых частых ошибок, с которой сталкивается каждый, кто хоть раз делал запрос с фронтенда на другой домен. Разберём, что это за механизм, почему браузер блокирует запрос, который прекрасно проходит через curl или Postman, и как это чинить на стороне сервера.
CORS (Cross-Origin Resource Sharing) — это механизм браузерной безопасности, который контролирует, может ли веб-страница с одного домена (origin) делать запросы к ресурсам на другом домене. Без CORS любой сайт мог бы из браузера пользователя слать запросы к произвольным API, используя его авторизационные cookie — классический вектор атаки.
Origin определяется тройкой: протокол + домен + порт. https://example.com и http://example.com — разные origin. https://example.com и https://api.example.com — тоже разные. Даже https://example.com:3000 и https://example.com:8080 — разные origin.
Важный момент, который часто путают: CORS — это ограничение браузера, а не сервера. Сервер всегда может ответить на запрос с любого origin — это браузер на основе заголовков ответа решает, отдавать ли результат JavaScript-коду страницы. Именно поэтому curl и Postman «не видят» проблему: они не применяют политику CORS, это чисто браузерное поведение.
Не каждый кросс-доменный запрос обрабатывается одинаково.
Simple request — выполняется напрямую, без предварительной проверки, если соблюдены условия: метод GET, HEAD или POST, и используются только «простые» заголовки (Accept, Accept-Language, Content-Type с одним из трёх базовых значений вроде application/x-www-form-urlencoded).
Preflight request — если запрос не подходит под условия simple (например, метод PUT/DELETE, кастомные заголовки вроде Authorization или Content-Type: application/json), браузер сначала отправляет служебный запрос методом OPTIONS, чтобы спросить у сервера разрешение, и только потом — реальный запрос.
OPTIONS /api/users HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: content-type, authorization
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: content-type, authorization
Access-Control-Max-Age: 86400
Если сервер не ответил корректными заголовками на preflight — реальный запрос браузер вообще не отправит, а в консоли появится та самая ошибка про CORS policy.
* разрешает любой origin, но только для запросов без credentials (cookie, авторизационные заголовки). Конкретный origin указывается явно, если нужны cookie/авторизация.Access-Control-Allow-Origin обязан содержать конкретный origin, а не * — браузер заблокирует комбинацию credentials + wildcard origin.Полный список текущих заголовков на любом сайте удобно смотреть через анализатор HTTP-заголовков — иногда проблема не в логике CORS, а в том, что заголовок просто не долетает до клиента из-за прокси или CDN, который его обрезает.
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Эта комбинация недопустима по спецификации — браузер заблокирует запрос, даже если сервер её отдал. Решение: указывать origin явно (можно динамически, на основе разрешённого списка доменов на бэкенде), а не статичную звёздочку.
Иногда middleware обрабатывает заголовки только для «реальных» запросов (GET/POST), забывая ответить на OPTIONS. В итоге preflight падает с 404 или 401 (потому что роут для OPTIONS не существует или требует авторизацию, которую браузер ещё не передаёт на этапе preflight).
Частый случай — middleware добавляет CORS-заголовки для успешных ответов (200), но не для ошибок (4xx, 5xx). В итоге при ошибке сервера в консоли видна не реальная причина (например, 500 на бэкенде), а маскирующая её ошибка CORS — потому что браузер блокирует доступ к телу ответа без нужных заголовков.
Чтобы поддерживать несколько доменов (например, app.example.com и admin.example.com), часто отражают Origin запроса обратно в Access-Control-Allow-Origin без проверки. Это фактически равнозначно wildcard, но с возможностью использовать credentials — серьёзная дыра в безопасности, если список разрешённых origin не валидируется явно по белому списку.
location /api/ {
set $cors_origin "";
if ($http_origin ~* "^https://(app|admin)\.example\.com$") {
set $cors_origin $http_origin;
}
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization' always;
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Max-Age' 86400;
add_header 'Content-Length' 0;
return 204;
}
proxy_pass http://backend;
}
const cors = require('cors');
const allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];
app.use(cors({
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
}));
Библиотека cors сама обрабатывает preflight-запросы — отдельный роут для OPTIONS писать не нужно.
OPTIONS).Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, соответствуют ли они тому, что реально запрашивает клиент. Удобно прогнать запрашиваемый URL через CORS-чекер, чтобы быстро увидеть, какие заголовки реально приходят в ответе.OPTIONS) отвечает 2xx и нужными заголовкамиAccess-Control-Allow-Origin: * с Access-Control-Allow-Credentials: trueAccess-Control-Max-Age, чтобы не слать лишние preflight-запросы при каждом обращенииCORS — не баг и не препятствие, которое нужно «обойти», а осознанный механизм безопасности браузера. Большинство проблем сводится к нескольким типичным причинам: неотвеченный preflight, неверная комбинация wildcard с credentials или потерянные на прокси заголовки. Проверьте свою текущую конфигурацию через CORS-чекер — обычно проблема находится за пару минут, если знать, на какие заголовки смотреть.