Как исправить ошибки CORS
“Blocked by CORS policy” — это защита ваших пользователей со стороны браузера, а не ошибка в вашем коде. Исправление всегда делается на сервере, который принимает запрос. В этом руководстве объясняется, что проверяет браузер, и приводятся рабочие конфигурации для типичных случаев.
Что такое CORS на самом деле
Браузеры запрещают JavaScript на https://app.example.com читать ответы от другого origin (схема + хост + порт), например https://api.example.com, если сервер не разрешил это. Сервер делает это с помощью заголовков ответа Access-Control-Allow-*. Запросы из curl, Postman или вашего бэкенда не затрагиваются, поэтому «в Postman работает», а в браузере нет.
Читайте текст ошибки
- “No ‘Access-Control-Allow-Origin’ header is present” — сервер вообще не отправил заголовки CORS или вернул страницу ошибки (404, 500) без них.
- “Response to preflight request doesn’t pass access control check” — запрос
OPTIONSзавершился с ошибкой: он не был обработан, вернул ошибку или не содержал нужных заголовков. - “The value of the ‘Access-Control-Allow-Origin’ header must not be the wildcard ‘*’ when credentials mode is ‘include’” — вы отправляете cookies, поэтому сервер должен вернуть точный origin, а не звездочку.
Посмотрите неудачный запрос в DevTools → Network или проверьте реальные заголовки ответа с помощью HTTP Header Checker.
Простые запросы и preflight
Запрос считается «простым», если это GET, HEAD или POST только с базовыми заголовками и телом в формате формы или простого текста. Всё остальное — Content-Type: application/json, заголовок Authorization, PUT или DELETE — заставляет браузер сначала отправить OPTIONS-запрос preflight. Ваш сервер должен ответить на него статусом 204 или 200 и такими заголовками:
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: 600
Рабочие конфигурации
Express с пакетом cors:
const cors = require('cors');
app.use(cors({
origin: ['https://app.example.com'],
credentials: true, // only if you send cookies
methods: ['GET', 'POST', 'PUT', 'DELETE'],
}));
Nginx:
location /api/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
add_header Access-Control-Max-Age 600;
return 204;
}
add_header Access-Control-Allow-Origin "https://app.example.com" always;
proxy_pass http://backend;
}
Флаг always важен: без него Nginx не добавляет заголовок в ответах с ошибками, и браузер сообщает об ошибке CORS вместо реального 500. Сгенерировать заголовки для других серверов (Vercel, Netlify, Cloudflare Workers, Apache) можно с помощью CORS Header Generator.
Cookies и учетные данные
Чтобы отправлять cookies между разными origin, запрос должен содержать credentials: 'include' (fetch), а сервер должен отвечать Access-Control-Allow-Credentials: true и точным origin — никогда *. Сама cookie должна иметь SameSite=None; Secure. Если вы подставляете origin из списка, добавьте также Vary: Origin, чтобы кэши не отдавали заголовки одного сайта другому.
Чего не делать
- Не возвращайте любой заголовок
Originобратно при включенных учетных данных — так любой сайт сможет читать данные ваших пользователей. - Не «исправляйте» CORS расширением браузера или публичным прокси: это только скрывает проблему на вашем компьютере.
- Если API ваш и находится на том же сайте, обратный прокси (тот же origin) полностью убирает необходимость в CORS.
Повторите запрос браузера с помощью API Tester или превратите его в код с помощью cURL to Code.