Сеть
Конструктор и проверка CORS
Настройка CORS без проб и ошибок: конфиг для nginx, Express и Apache по списку источников, и пошаговая проверка запроса по спецификации Fetch — нужен ли preflight, пропустит ли браузер ответ и какие заголовки увидит JavaScript.
Политика
По одному на строку: https://app.example.com. «*» — всем (без cookie).
Access-Control-Allow-Headers
Access-Control-Expose-Headers
Chrome держит не дольше 7200
nginx
# В блок http {}
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 Vary Origin always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Expose-Headers "X-Total-Count" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
add_header Access-Control-Max-Age 7200 always;
return 204;
}
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Vary Origin always;
add_header Access-Control-Allow-Credentials true always;
add_header Access-Control-Expose-Headers "X-Total-Count" always;Express (пакет cors)
import cors from 'cors';
app.use(cors({
origin: ['https://app.example.com', 'https://admin.example.com'],
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['X-Total-Count'],
credentials: true,
maxAge: 7200,
}));Apache (.htaccess)
SetEnvIf Origin "^(https://app\.example\.com|https://admin\.example\.com)$" CORS_ORIGIN=$0
Header always set Access-Control-Allow-Origin "%{CORS_ORIGIN}e" env=CORS_ORIGIN
Header always merge Vary Origin
Header always set Access-Control-Allow-Credentials "true"
Header always set Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE"
Header always set Access-Control-Allow-Headers "Content-Type, Authorization"
Header always set Access-Control-Expose-Headers "X-Total-Count"
Header always set Access-Control-Max-Age "7200"
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=204,L]Что увидит браузер
HTTP/1.1 204 No Content Access-Control-Allow-Origin: https://app.example.com Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE Access-Control-Allow-Headers: Content-Type, Authorization Access-Control-Max-Age: 7200 Vary: Origin HTTP/1.1 200 OK Content-Type: application/json Access-Control-Allow-Origin: https://app.example.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: X-Total-Count Vary: Origin X-Total-Count: …
Почему это делает браузер, а не сервер
CORS не защищает сервер: запрос всё равно доходит до него, а curl и Postman заголовков CORS не проверяют. Это браузер решает, отдать ли ответ скрипту чужого сайта. Поэтому «в Postman работает, а на сайте нет» — почти всегда нехватка одного из Access-Control-* в ответе или отказ на preflight.