На главную

Сеть

Конструктор и проверка 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.