- По умолчанию браузер запрещает JS читать ответ с другого origin (Same-Origin Policy). CORS — способ сервера это разрешить.
- Origin = схема + хост + порт. Отличается хоть один → cross-origin.
- Разрешает СЕРВЕР заголовком
Access-Control-Allow-Origin. Блокирует БРАУЗЕР, не сервер. - «Простые» запросы идут сразу; «сложные» — сначала preflight (
OPTIONS). - CORS защищает пользователя (его куки/сессию), а не сервер. Это не серверная защита.
- С куками (
credentials): нуженAllow-Credentials: true+ конкретный origin (не*).
Корень: Same-Origin Policy
CORS существует только потому, что есть SOP. Сначала — про неё.
Браузер по умолчанию не даёт скрипту читать ответы с другого источника — чтобы зловредный сайт не сходил от твоего имени (с твоими куками) в твою почту/банк и не прочитал данные. Это Same-Origin Policy. CORS — это контролируемое ослабление SOP: сервер явно говорит «этому origin читать можно».
fetch/XHR— JS не прочитает ответ cross-origin- чтение чужого
iframeDOM
<img>,<script src>,<link>- отправка
<form>на чужой домен - встраивание
<iframe>(без чтения)
Что такое origin
Origin = схема + хост + порт. Совпасть должны все три.
URL запроса (из https://site.com) | Same-origin? |
|---|---|
https://site.com/api | ✅ да |
http://site.com (другая схема) | ❌ нет |
https://api.site.com (поддомен) | ❌ нет |
https://site.com:8443 (другой порт) | ❌ нет |
Кто блокирует и кого защищает
Любимый вопрос «кто инициатор ошибки CORS».
Блокирует — браузер
Сервер часто получает запрос и обрабатывает. Браузер просто не отдаёт ответ скрипту, если нет нужных заголовков. Ошибка — на клиенте.
Решает — сервер
Разрешение даёт сервер заголовками Access-Control-*. Фронт «пофиксить CORS у себя» не может — только сервер или прокси.
curl, Postman, сервер-к-серверу его игнорируют. Защищает он пользователя в браузере, а не API от хакера.Простые vs сложные запросы
От этого зависит, будет ли preflight.
- метод
GET,POSTилиHEAD - только «безопасные» заголовки
Content-Type:text/plain,multipart/form-dataилиx-www-form-urlencoded
- метод
PUT/DELETE/PATCH - кастомный заголовок (
Authorization* ,X-…) Content-Type: application/json
OPTIONS.Preflight: предварительный OPTIONS
Браузер сам спрашивает у сервера разрешение ДО основного запроса.
OPTIONSс Origin, Access-Control-Request-Method, Access-Control-Request-HeadersAllow-Origin, Allow-Methods, Allow-Headers, Max-AgeOPTIONS /api/user Ответ сервера:
Origin: https://site.com Access-Control-Allow-Origin: https://site.com
Access-Control-Request- Access-Control-Allow-Methods: PUT, GET
Method: PUT Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Request- Access-Control-Max-Age: 600
Headers: authorization
Access-Control-Max-Age кэширует preflight (в секундах) → браузер не шлёт OPTIONS на каждый запрос. Так «обходят лишний OPTIONS».Заголовки CORS
Что шлёт браузер и чем отвечает сервер.
| Заголовок (ответ сервера) | Что значит |
|---|---|
Access-Control-Allow-Origin | кому можно: конкретный origin или * |
Access-Control-Allow-Methods | разрешённые методы (для preflight) |
Access-Control-Allow-Headers | разрешённые кастомные заголовки |
Access-Control-Allow-Credentials | true → можно слать куки |
Access-Control-Expose-Headers | какие заголовки ответа JS может прочитать |
Access-Control-Max-Age | сколько секунд кэшировать preflight |
Origin, а в preflight ещё Access-Control-Request-Method/Headers.Куки и credentials
Самое узкое место — когда нужно слать сессионные куки cross-origin.
По умолчанию fetch не шлёт куки на другой origin. Чтобы слать: fetch(url, { credentials: 'include' }) (или xhr.withCredentials = true).
•
Allow-Origin не может быть * — только конкретный origin.•
Allow-Credentials: true обязателен.•
Allow-Headers/Allow-Methods тоже не *.• Сама кука должна быть
SameSite=None; Secure, иначе браузер её не отправит cross-site.Как чинят CORS на практике
| Способ | Когда |
|---|---|
| Настроить заголовки на сервере/API | правильный путь (prod) |
Dev-прокси (Vite server.proxy, webpack devServer) | локальная разработка — браузер думает, что same-origin |
| Серверный прокси / BFF | фронт ходит на свой бэкенд, тот — на чужой API (server-to-server без CORS) |
| Reverse-proxy (nginx) добавляет заголовки | когда нет доступа к коду API |
CORS vs CSRF (не путать)
Связка из реальных собесов (РСХБ ИНТЕХ: «CORS, CSRF, защита от CSRF»).
CORS
Про чтение чужого ответа из JS. Ослабляет SOP. Защищает данные пользователя от чужого скрипта.
CSRF
Атака: чужой сайт отправляет запрос от твоего имени (куки прикрепятся сами). CORS тут не спасает — простую форму можно слать без preflight.
SameSite=Lax/Strict у кук + CSRF-токен (сервер проверяет секрет, которого чужой сайт не знает). Не CORS.Подводные камни
- «CORS чинят на фронте» — нет, на сервере/прокси
- «CORS защищает API» — нет, это правило браузера
- «Ошибка CORS = запрос не дошёл» — простой мог дойти, не прочитан ответ
*+credentialsвместе — запрещено- забыть обработать
OPTIONSна сервере
- Разрешения — заголовки сервера
- CORS = защита пользователя, не сервера
- Сложный запрос с preflight не дойдёт, если OPTIONS не прошёл
- С куками — конкретный origin + Allow-Credentials
- Сервер отвечает на preflight
OPTIONS
Реальные вопросы с собесов
Из нашей базы 2000+ собесов.
• Что такое CORS, зачем нужен? — Сбер, OCS Distribution, Digital Biz Factory, Цифровая мануфактура
• CORS, preflight (OPTIONS) — что это, как работает — Техно Диасофт, BRAINSHELL, АГ-Логистика
• Кто инициатор ошибки CORS? — Sminex
• CORS и поддомены, заголовки — АФЛТ Системс
• Как «обойти» CORS — МоеVideo
• CORS + CSRF, способы защиты от CSRF — РСХБ ИНТЕХ
• CSRF-атаки, CSRF-токен — для чего — WMT, Согаз, Сбер