CORS: полное руководство по Access-Control-Allow
CORS: полное руководство по Access-Control-Allow
CORS (Cross-Origin Resource Sharing) — механизм, которым сервер разрешает браузеру использовать свои ресурсы из другого origin. Без CORS любой JS с чужого домена не может прочитать ответ вашего API документацию — это фундамент Same-Origin Policy. В этой статье разберём preflight-запрос, заголовки Access-Control-Allow-*, работу с credentials и типичные ошибки вида «has been blocked by CORS policy».
Same-Origin Policy и зачем нужен CORS
Origin — это тройка (scheme, host, port). https://app.example.com и https://api.example.com — разные origin. По умолчанию браузер запрещает JS читать ответы cross-origin fetch. CORS — официальный способ сервера сказать: «мне ok, что к этому ресурсу обращаются с другого origin». Стандарт описан в WHATWG Fetch Standard.
Простые запросы
Запрос считается «простым», если метод — GET/POST/HEAD, нет кастомных заголовков, Content-Type — один из application/x-www-form-urlencoded, multipart/form-data, text/plain. Сервер возвращает:
Access-Control-Allow-Origin: https://app.example.com
Vary: Origin
Vary: Origin критичен, если origin динамический — иначе CDN закэширует ответ для первого origin и отдаст его всем остальным.
Preflight OPTIONS
Сложный запрос (PUT/DELETE/PATCH, кастомные заголовки, JSON body) браузер предваряет OPTIONS-запросом. Сервер должен ответить заголовками разрешений:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH
Access-Control-Allow-Headers: Content-Type, Authorization, X-API-Key
Access-Control-Max-Age: 86400
Max-Age кэширует preflight на стороне браузера — уменьшает latency на 50+ мс на каждый запрос.
Credentials (cookies, Authorization)
Чтобы отправить куки или заголовок Authorization, клиент делает fetch(..., { credentials: 'include' }), а сервер — возвращает:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Важно: с Allow-Credentials: true НЕЛЬЗЯ использовать Access-Control-Allow-Origin: * — только явный origin. Это защита от кражи данных.
nginx
map $http_origin $cors_origin {
default "";
"https://app.example.com" $http_origin;
"https://admin.example.com" $http_origin;
}
location /api/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, PATCH" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Max-Age "86400" always;
add_header Vary "Origin" always;
return 204;
}
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Vary "Origin" always;
proxy_pass http://backend;
}
Express и NestJS
// Express
import cors from 'cors';
app.use(cors({
origin: ['https://app.example.com', 'https://admin.example.com'],
credentials: true,
methods: ['GET','POST','PUT','DELETE','PATCH'],
maxAge: 86400,
}));
// NestJS
app.enableCors({
origin: (origin, cb) => {
const allow = ['https://app.example.com'];
if (!origin || allow.includes(origin)) cb(null, true);
else cb(new Error('CORS blocked'));
},
credentials: true,
});
Типичные ошибки
«No Access-Control-Allow-Origin header» — сервер не поставил заголовок. Проверить curl -I -H "Origin: https://app.example.com" https://api.example.com/.
«Response has wildcard but credentials include» — либо уберите credentials, либо замените * на явный origin.
Кэш отдаёт неправильный Allow-Origin — добавьте Vary: Origin.
OPTIONS возвращает 401 — preflight идёт без auth-токенов; сделайте исключение в auth middleware для OPTIONS.
Отладка
Проверка в консоли браузера → Network → смотрите preflight и ответ. В терминале:
curl -i -X OPTIONS \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type,Authorization" \
https://api.example.com/endpoint
CORS Checker enterno.io эмулирует произвольный Origin и показывает ответ сервера без кода на фронте.
FAQ
CORS это защита сервера? Нет. CORS — ограничение браузера, которое сервер может ослабить. Curl и любой серверный HTTP-клиент игнорирует CORS.
Можно ли Access-Control-Allow-Origin: *? Только для публичных API без credentials (CDN-ресурсы, open data).
Что блокирует preflight? Чаще всего — отсутствующий Access-Control-Allow-Headers для заголовка Authorization или X-API-Key.
Нужен ли CORS внутри одного origin? Нет — same-origin запросы CORS не трогает.
Вывод
CORS — не антивирус, а декларация. Держите whitelist origin, всегда Vary: Origin, никогда не комбинируйте * + credentials, добавьте мониторинг на CORS-заголовки и проверяйте CORS Checker. Связанные темы: HTTP Security Headers, rate limiting API.
Проверьте ваш сайт прямо сейчас
Проверить →