Перейти к содержимому

Sop и Cors в браузере: как работает origin и почему возникает ошибка Cors

SOP & CORS: как браузер решает, что "своё", а что "чужое"

Представьте простой эксперимент: держите открытыми две вкладки. В первой - интернет‑банк, где вы уже вошли в аккаунт и видите баланс. Во второй - случайный сайт, на который вы попали из поисковой выдачи. Вопрос звучит почти параноидально, но именно в нём прячется база веб‑безопасности: что мешает скрипту со второй вкладки тихо забрать баланс из первой и отправить его куда угодно?

Если разложить ситуацию "по железу", кажется, что мешать нечему. Оба сайта живут в одном браузере. Браузер умеет делать запросы. Скрипт на случайной странице способен сформировать запрос к домену банка, а браузер автоматически приложит к нему ваши cookies - те самые, по которым банк узнаёт пользователя. Ответ с цифрами вернётся. Казалось бы, осталось только прочитать результат и переслать его злоумышленнику. Технически цепочка выглядит пугающе простой.

Но в реальности так не происходит почти никогда - не потому, что каждый банк идеален, а потому что в браузерах по умолчанию встроено правило, которое включено всегда, даже когда разработчик о нём не думает. Это Same‑Origin Policy (SOP). Разобраться в нём - лучший способ понять, почему затем всех "догоняет" CORS и почему вокруг него так много мифов. В практическом разборе, где SOP разложен по полочкам и показаны типовые ловушки, удобно ориентироваться по тексту SOP и CORS что это на примерах из браузера - там хорошо видно, где именно возникает путаница.

Что браузер считает "одним сайтом": понятие origin

Человеческая интуиция подводит: "один сайт" для пользователя и "один сайт" для браузера - не одно и то же. Браузер опирается на строгое понятие origin, которое складывается ровно из трёх частей:

- протокол (http/https),
- домен,
- порт.

Совпали все три - значит, origin один. Разошлась хотя бы одна составляющая - перед вами уже разные origin, и SOP начинает работать "в полный рост".

Отсюда вытекают типовые неожиданности:

- `https://shop.ru/catalog` и `https://shop.ru/cart` - один origin (путь не важен).
- `http://shop.ru` и `https://shop.ru` - разные origin (разный протокол).
- `https://shop.ru` и `https://api.shop.ru` - тоже разные origin (поддомен для браузера - другой домен).

Именно последний пункт чаще всего превращается в головную боль: фронтенд на основном домене и API на поддомене кажутся "родными", но в терминах SOP они чужие.

SOP: запрещено не "ходить", а "читать"

Ключ к пониманию прост: Same‑Origin Policy запрещает коду, выполняющемуся в одном origin, читать данные из другого origin. Но SOP не запрещает загружать чужое.

Это различие критично. Веб‑страницы постоянно подтягивают контент "извне": изображения, шрифты, скрипты, видео, пиксели аналитики. Загрузка как факт возможна, иначе современный интернет рассыпался бы. Запрет включается в момент, когда скрипт пытается получить доступ к содержимому ответа - например, прочитать JSON из чужого API через `fetch` и использовать его в коде.

Вернёмся к вкладкам с банком. Скрипт со случайного сайта действительно может инициировать запрос к банку, браузер может приложить cookies, и сервер может вернуть корректный ответ. Но прочитать этот ответ JavaScript не сможет: браузер остановит доступ к данным, потому что origin разные. Именно так SOP "обрезает" возможность массового выноса приватных данных между вкладками.

CORS: разрешение выдаёт не браузер и не "клиент"

Дальше появляется CORS (Cross‑Origin Resource Sharing) - механизм, который регулирует, когда браузер всё‑таки может отдать JS‑коду доступ к ответу от другого origin. И здесь начинается популярная ошибка восприятия: многие думают, что CORS - это "защита сервера от чужих запросов". На практике CORS почти всегда - это политика в браузере, которая решает, сможет ли страница прочитать ответ.

То есть CORS - не стена, а договорённость: сервер в ответе явно сообщает, каким origin разрешено читать результат. Делается это через CORS заголовки Access-Control-Allow-Origin и связанные с ними директивы (методы, заголовки, креденшелы). Если сервер такие заголовки не отдал - браузер не предоставит ответ JavaScript‑коду, даже если запрос технически прошёл и сервер вернул 200 OK.

Отсюда следует важное: CORS "не защищает" ваш API от запросов как таковых. Злоумышленник может обращаться к API не из браузера (через сервер, скрипт, curl) - и CORS ему не помешает. CORS регулирует именно сценарий "страница в браузере пытается прочитать чужой ответ".

Откуда берётся preflight и почему кажется, что "лишний запрос" появился сам

Когда запрос выходит за рамки "простого", браузер может выполнить preflight - предварительную проверку через `OPTIONS`. Она нужна, чтобы заранее выяснить у сервера: можно ли отправлять основной запрос с таким методом, заголовками и параметрами. Поэтому иногда разработчик видит в DevTools "лишний" запрос, который он явно не писал, и удивляется, почему API внезапно получает два обращения вместо одного.

Preflight чаще всего всплывает при нестандартных заголовках, методах (например, `PUT`, `PATCH`, `DELETE`) или при отправке JSON с определённым `Content-Type`. Это не баг и не "проделки браузера", а часть CORS‑логики.

Типовой сценарий: "ошибка CORS как исправить" без магии

Когда в консоли появляется CORS‑ошибка, разработчик обычно пытается "починить фронтенд": поменять `fetch`, включить режим `no-cors`, поставить прокси "на всякий случай". Но в большинстве случаев ответ на вопрос "ошибка CORS как исправить" лежит на стороне сервера: он должен корректно вернуть заголовки, которые разрешат чтение ответа нужному origin.

Практически это упирается в то, как настроить CORS в вашем бэкенде или на шлюзе (nginx, API‑gateway, CDN). Минимально нужно, чтобы сервер отвечал заголовком `Access-Control-Allow-Origin` (точным значением домена фронтенда или списком через логику на сервере), а для запросов с учётными данными - ещё и `Access-Control-Allow-Credentials: true` с аккуратной настройкой cookies.

Удобно держать перед глазами короткий конспект, где увязаны SOP, preflight и заголовки ответа - например, как настроить CORS без путаницы с origin: там хорошо видно, почему "разрешить всё звёздочкой" не всегда работает и когда это вообще опасно.

Когда сервер CORS-заголовки не отдаёт: что реально можно сделать

Иногда вы упираетесь в чужой API, который принципиально не отдаёт CORS‑заголовки. И это не "вредность" - часто авторы API просто не предполагают браузерные клиенты и не хотят, чтобы любой сайт мог напрямую читать их ответы.

В таком случае вариантов немного и все они "архитектурные":

1) Делать запросы к чужому API со своего сервера (backend‑to‑backend), а в браузер отдавать уже свой ответ.
2) Использовать официально предусмотренный механизм авторизации/прокси у провайдера API (если он есть).
3) Если вы контролируете инфраструктуру - ставить свой прокси‑слой и добавлять заголовки уже на нём (но это имеет смысл только когда у вас есть право так делать).

Попытки "обойти CORS" на стороне браузера обычно либо не работают, либо превращаются в небезопасные решения.

SOP, CSRF и почему "не прочитать" не равно "не сделать"

Отдельная ловушка: SOP блокирует чтение ответа, но не обязательно блокирует сам факт выполнения действия. Поэтому рядом с SOP всегда всплывает тема "защита от CSRF и SOP". Браузер может отправить запрос с cookies на чувствительную операцию (например, смену email), а чтение ответа будет запрещено - но операция на сервере всё равно может выполниться, если у бэкенда нет CSRF‑защиты (токены, SameSite‑cookies, проверка origin/referer и т. п.).

Отсюда практический вывод: SOP и CORS - это про границы чтения данных в браузере, а серверная безопасность (в том числе CSRF) должна жить своей полноценной жизнью. Нельзя "надеяться на CORS" как на универсальный щит - он не про это.

Частые места, где обжигаются

1) Фронтенд и API на разных поддоменах: кажется "один продукт", а для браузера - разные origin.
2) Неверно выставленный `Access-Control-Allow-Origin`: например, `*` вместе с `Allow-Credentials` (так нельзя).
3) Не обработан `OPTIONS` и не возвращены нужные заголовки на preflight.
4) Смешение понятий: CORS не блокирует доступ к API из Postman/curl, он ограничивает чтение ответа из браузерного JS.

Если держать в голове базовую схему "origin → SOP запрещает чтение → CORS может разрешить чтение при явном согласии сервера", большая часть загадок исчезает сама собой.

Scroll to Top