Чтобы понять CORS, нужно сначала понять, чему он позволяет случиться. А именно — same-origin policy, «правилу одинакового происхождения». Это базовая защита, которая в браузерах действует с конца девяностых.
Что такое origin
Origin — это схема + хост + порт URL. Например:
https://fruntend.com/articles/foo → https://fruntend.com
https://fruntend.com:8080/api → https://fruntend.com:8080
http://localhost:3000/posts/1 → http://localhost:3000
Path, query, fragment в origin не входят. Меняются только три параметра, и если хотя бы один из них отличается — это другой origin.
| Сравнение | Тот же origin? | Почему |
| https://fruntend.com и https://fruntend.com | Да | Всё совпадает |
| https://fruntend.com и https://fruntend.com/posts/1 | Да | Path в origin не входит |
| https://fruntend.com и http://fruntend.com | Нет | Разная схема |
| https://fruntend.com и https://api.fruntend.com | Нет | Разный поддомен — разный хост |
| https://fruntend.com и https://fruntend.com:8080 | Нет | Разный порт (явный 8080 vs дефолтный 443) |
Само правило
Same-origin policy формулируется одной фразой: JS на странице одного origin не может прочитать ответы запросов к другому origin.
Зачем это нужно. Представьте: вы залогинены в свой банк во вкладке 1. В соседней вкладке открыли левый сайт с котиками. Если бы JS котикового сайта мог через fetch сходить на сайт банка с вашими куками и прочитать ответ — он бы тут же увидел ваш баланс, историю операций, всё. Same-origin policy этого не даёт.
Запрос уйти может, кука даже прицепится — но JS не получит доступа к ответу. Браузер увидит, что origin сайта (котики) и origin цели (банк) разные, и заблокирует чтение результата.
Что разрешено без всякого CORS
Из этого правила есть исторические исключения — которые задумывались задолго до появления API и теперь работают по особым правилам.
- Картинки (<img src="https://other.com/pic.jpg">) — можно вставлять с любого домена. Прочитать пиксели через canvas уже нельзя без CORS, но просто показать — разрешено.
- Скрипты (<script src="https://cdn.example.com/lib.js">) — так живут все CDN. Скрипт выполняется в контексте вашей страницы.
- Стили (<link rel="stylesheet" href="...">) — шрифты, темы, CSS-сетки с других доменов.
- Формы (<form action="https://other.com/submit" method="POST">) — могут постить на любой домен (это и есть основа CSRF; см. главу 7.4).
- iframe — может загрузить страницу с любого origin, но JS не сможет читать его содержимое (без отдельного контракта через postMessage).
JS-запросы (fetch, XMLHttpRequest) исторически появились позже и сразу пошли по строгому пути same-origin. Чтобы их пускать, и был придуман CORS.
CORS = «согласие сервера на чтение»
CORS — Cross-Origin Resource Sharing. Это механизм, через который сервер говорит браузеру: «разрешаю вот этим origin читать мои ответы».
Происходит так:
- Браузер шлёт запрос с заголовком Origin: https://fruntend.com — это «паспорт страницы».
- Сервер видит Origin и решает: пускать или нет. Если да — в ответе ставит заголовок Access-Control-Allow-Origin: https://fruntend.com (или *, если разрешено всем).
- Браузер проверяет ответ: ага, разрешение есть — отдаёт ответ JS-у. Нет разрешения — блокирует, в JS приходит ошибка.
Важно: запрос всегда уходит на сервер. CORS блокирует только чтение ответа. Запрос сервер обрабатывает, побочные эффекты (создание поста, отправка email) могут случиться. Поэтому CORS — не защита от CSRF; для CSRF есть SameSite-куки и токены.
JSONPlaceholder и публичные API
Когда мы в модулях 4–5 свободно ходили на jsonplaceholder.typicode.com и dog.ceo — никаких CORS-ошибок не было. Потому что эти API специально ставят разрешающий заголовок:
Access-Control-Allow-Origin: *
Звёздочка значит «разрешаю всем». Для публичного учебного API это идеально. Для закрытых сервисов — так делать опасно: любой сайт мира сможет дёргать ваш бэк.
В следующей главе разберём, как браузер устраивает предварительную проверку для более сложных запросов.