CORS
Cross-Origin Resource Sharing — механизм, которым сервер разрешает браузеру отдавать ответ коду со стороннего домена.
Что такое CORS
CORS расшифровывается как Cross-Origin Resource Sharing — совместное использование ресурсов между источниками. Это набор HTTP-заголовков, которыми сервер сообщает браузеру: коду с такого-то чужого домена можно читать мои ответы.
Механизм существует из-за same-origin policy — базового правила безопасности браузера: скрипт со страницы одного источника не может читать ответы от другого. Источник (origin) — это тройка «схема + хост + порт», и совпадать должны все три:
Со страницы https://app.example.com |
Тот же источник? |
|---|---|
https://app.example.com/api/orders |
да |
https://api.example.com/orders |
нет — другой хост |
http://app.example.com/orders |
нет — другая схема |
https://app.example.com:8443/orders |
нет — другой порт |
Без same-origin policy открытая вкладка со зловредным сайтом могла бы отправить запрос в вашу почту — вместе с вашими куками — и прочитать ответ. CORS не «защита от хакеров», а способ аккуратно ослабить это правило там, где вы сами этого хотите.
Ключевой момент, экономящий часы отладки: CORS живёт только в браузере. Запрос из curl, Python-скрипта или с сервера на сервер CORS-проверок не проходит. Если curl работает, а браузер ругается — дело именно в CORS, а не в сервере.
Как это выглядит
Браузер добавляет к запросу заголовок Origin, сервер отвечает разрешением:
GET /v1/orders HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Нет заголовка в ответе — браузер получит данные, но не отдаст их коду и напишет в консоль ошибку. Именно поэтому во вкладке Network запрос выглядит успешным (200), а fetch при этом падает — HAR-файл хорошо показывает эту картину.
Preflight-запрос
Для «непростых» запросов браузер сначала спрашивает разрешение отдельным запросом методом OPTIONS — это preflight. Он срабатывает, если метод не GET/HEAD/POST, если Content-Type: application/json, или если есть кастомные заголовки вроде Authorization или X-API-Key. То есть практически любой вызов REST API вызывает preflight.
OPTIONS /v1/orders HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
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, PATCH, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
Access-Control-Max-Age позволяет браузеру закешировать разрешение и не слать OPTIONS перед каждым вызовом — без него число запросов к API удваивается.
Настройка в Nginx
location /v1/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "https://app.example.com";
add_header Access-Control-Allow-Methods "GET, POST, PATCH, DELETE";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Access-Control-Max-Age 86400;
return 204;
}
add_header Access-Control-Allow-Origin "https://app.example.com" always;
proxy_pass http://backend;
}
Директива always обязательна: без неё Nginx не добавит заголовок к ответам с кодами ошибок, и вместо честного 500 фронтенд увидит невнятную CORS-ошибку.
Типичные грабли
*вместе с credentials не работает. Если запрос идёт с куками (credentials: "include"), браузер требует конкретный источник иAccess-Control-Allow-Credentials: true. Звёздочка запрещена — подставляйте домен из заголовкаOrigin, сверяясь со списком разрешённых. Возвращать любой пришедшийOriginбез проверки — то же самое, что*, только выглядит аккуратнее.Access-Control-Allow-Origin: *на приватном API. Это разрешение любому сайту дёргать ваш API из браузера пользователя. Для публичной read-only выдачи нормально, для личного кабинета — нет.OPTIONSзакрыт авторизацией. Preflight приходит без токена, поэтому401наOPTIONSломает вообще все запросы. Метод должен отвечать до проверки авторизации.- Заголовки в ответе не видны JS. Кастомный заголовок вроде
X-Total-Countнужно явно перечислить вAccess-Control-Expose-Headers. - Дубли заголовка. Если CORS отдают и Nginx, и фреймворк, приходят два
Access-Control-Allow-Origin— браузер отвергает такой ответ. Настраивайте в одном месте.
Связанные термины
- HTTP — заголовки и метод OPTIONS, на которых построен CORS
- Веб-разработка — где возникает разделение фронтенда и API
- REST API — вызовы, вызывающие preflight
- Nginx — где чаще всего настраивают заголовки
- HAR-файл — чем диагностировать проблему
Готовы запустить GPU-задачу?
Запустить GPU-сервер