Чтобы понять 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 читать мои ответы».

Происходит так:

  1. Браузер шлёт запрос с заголовком Origin: https://fruntend.com — это «паспорт страницы».
  2. Сервер видит Origin и решает: пускать или нет. Если да — в ответе ставит заголовок Access-Control-Allow-Origin: https://fruntend.com (или *, если разрешено всем).
  3. Браузер проверяет ответ: ага, разрешение есть — отдаёт ответ JS-у. Нет разрешения — блокирует, в JS приходит ошибка.

Важно: запрос всегда уходит на сервер. CORS блокирует только чтение ответа. Запрос сервер обрабатывает, побочные эффекты (создание поста, отправка email) могут случиться. Поэтому CORS — не защита от CSRF; для CSRF есть SameSite-куки и токены.

JSONPlaceholder и публичные API

Когда мы в модулях 4–5 свободно ходили на jsonplaceholder.typicode.com и dog.ceo — никаких CORS-ошибок не было. Потому что эти API специально ставят разрешающий заголовок:

Access-Control-Allow-Origin: *

Звёздочка значит «разрешаю всем». Для публичного учебного API это идеально. Для закрытых сервисов — так делать опасно: любой сайт мира сможет дёргать ваш бэк.

В следующей главе разберём, как браузер устраивает предварительную проверку для более сложных запросов.