В чем заключается политика same-origin и зачем она нужна
Что проверяют
Понимаете ли вы, что именно запрещает браузер, а что нет. Путаница здесь типичная: SOP считают защитой от XSS или думают, что она не даёт отправить запрос. Следом почти всегда спрашивают про CORS: «Почему в Postman ответ приходит, а в браузере ошибка?»
Как отвечать
Origin это тройка: схема, хост, порт. https://site.ru и http://site.ru разные источники, site.ru и api.site.ru тоже разные, путь роли не играет.
Политика не даёт скрипту одного источника читать данные другого:
- ответы сетевых запросов:
fetchк чужому источнику без разрешения сервера завершается ошибкой, тело не прочитать; - DOM чужих окон и iframe: доступ к
contentWindow.documentдругого источника запрещён; - хранилища:
localStorageи IndexedDB разделены по источникам, у cookies свои правила, по домену и пути.
При этом встраивать и отправлять можно: <img>, <script>, <link>, отправка формы на другой домен. Поэтому SOP не защищает от CSRF, для этого нужны cookies с SameSite и CSRF-токены.
Зачем всё это: вы залогинены в банке, открываете чужой сайт, и без SOP его скрипт отправил бы запрос в банк с вашими cookies и прочитал ответ.
Легальный обмен идёт через CORS: сервер отвечает заголовком Access-Control-Allow-Origin. Для «непростых» запросов (методы вроде PUT и DELETE, свои заголовки, Content-Type: application/json) браузер сначала шлёт preflight OPTIONS. Между окнами работает postMessage с обязательной проверкой event.origin.
Где спотыкаются
Говорят, что SOP защищает от XSS. Внедрённый скрипт выполняется в контексте вашего же источника, и политика ему не мешает, тут работают экранирование и CSP.
Думают, что браузер блокирует сам запрос. Простой запрос доходит до сервера и выполняется, браузер лишь не отдаёт ответ скрипту. Если сервер меняет данные по такому запросу, SOP его не спасёт.
Лечат ошибку CORS на клиенте через mode: 'no-cors'. Ответ становится непрозрачным, прочитать его всё равно нельзя. Правится сервер или dev-прокси вроде server.proxy в Vite. И помните: с credentials: 'include' сервер не может отвечать *, нужен конкретный источник и Access-Control-Allow-Credentials: true.