Сколько раз вы сталкивались с ошибкой вида Access to ... from origin ... has been blocked by CORS policy.? Уверен, в вашей практике это происходит плюс-минус регулярно: браузер ругается, вы не понимаете, в чем дело, при этом бэкендеры говорят: «Я тестировал через постман, ручка отрабатывает, смотри у себя». В этот момент мы лезем крутить настройки прокси или пытаемся понять, что мы сделали не так, где оступились
Давайте разберемся с CORS раз и навсегда. Это суперсила браузера, которой нужно научиться пользоваться.
Базовые термины: CORS, SOP
CORS — Cross Origin Resource Sharing — браузерный механизм безопасности, который призван навести порядок при взаимодействии двух разных ресурсов. Грубо говоря, если Сайт А размещен на источнике А и делает запрос к API, размещенном там же, корсы тут отрабатывать не будут.
Можно представить, что мы берем что-то у себя из левого кармана и перекладываем в правый. Никто не может запретить нам сделать это. Но ситуация выглядит чуть более тревожно, если кто-то кладет что-то в наш карман. Даже если по нашей просьбе.
Вот и браузер включает оборону, если видит что запрос с источника А улетает на источник Б.
Здесь важно понять, как браузер различает источники. Тут и подходит время рассказать про второй базовый термит — Same Origin Policy или политику одного источника.
Origin, источник — своего рода идентификатор. Он складывается из трех частей:
- Протокол (
http://илиhttps://) - Домен (
мой-сайт.руилиlocalhost) - Порт (
:3000,:8080)
Если отличается хотя бы одна из этих трех частей, браузер считает источники разными
http://site.ruиhttps://site.ru— разные (другой протокол).http://site.ruиhttp://api.site.ru— разные (поддомен).http://localhost:3000иhttp://localhost:4000— разные (порт).
Именно поэтому запрос с localhost:3000 на localhost:4000 или на api.mycorp.ru может быть заблокирован.
Зачем все это нужно? Защищаемся от CSRF
Представим, что мы вернулись в прошлое и у нас уже есть интернет, но CORS еще не существует (кстати, они появились аж в 2004 году). Мы авторизованы в банке bank.ru. Во второй вкладке у нас открыта почта, куда упало письмо со зловредной ссылкой на сайт evilhacker.com. Мы переходим по ссылке, сайт загружается и на нем исполняется какой-нибудь такой скрипт:
fetch('https://bank.ru/transfer?to=hacker&amount=1000', {
credentials: 'include' // браузер прикрепит ваши cookies!
})
Да, такое апи банка выглядит дырявым и уязвимым (думаю автора метода, который переводить клиентские деньги по гет-запросу могли бы с позором уволить), но дело в другом. Напомню, что мы находимся в мире без CORS. Браузер послушно сходит на bank.ru, прикрепит ваши куки, и тот отправит деньги злоумышленнику.
Вернемся в сегодняшний день. CORS изобрели, bank.ru переписал метод на POST с отправкой параметров в теле запроса. Мы снова попадаемся на удочку злоумышленника и открываем его сайт, исполняем его скрипт. Но вот что происходит:
- браузер фоново отправляет preflight-запрос методом
OPTIONSна адресhttps://bank.ru/transer - браузер смотрит на заголовок
Access-Control-Allow-Originв ответе и ищет, есть ли в перечне допущенных к этому методу ресурсовhttp://evil.com - надеемся, что не находит и не отправляет запрос, блокируя его механизмом CORS
Это сработает для всех сложных запросов. К таковым относятся например те, которые отправляются с заголовком Content-Type отличным от application/x-www-form-urlencoded, multipart/form-data или text/plain, используют методы PUT / DELETE или собственные заголовки.
Разберем механизм CORS по шагам
Допустим, ваш фронт на front.ru:3000 стучится на back.ru.
1. Отправляем запрос:
fetch('https://back.ru/api/users')
- Браузер видит: Origin
front.ru:3000не совпадает сback.ru. Браузер добавляет в запрос заголовок:
Origin: http://front.ru:3000
3. Сервер back.ru получает запрос, смотрит на Origin и решает, пропускать ли его или нет. Если пускает, сервер добавляет в ответ заголовок:
Access-Control-Allow-Origin: http://front.ru:3000
4. Браузер получает ответ. Если заголовка Access-Control-Allow-Origin нет — браузер выбрасывает ошибку CORS
CORS на этапе разработки: прокси наше всё
Покажу на примере Vite. Просто добавим в vite.config.js:
proxy: {
'/api': 'http://localhost:5000'
}
Теперь с фронтенда мы будем стучаться на свой же http://localhost:3000/api, а Vite уже сам проксирует на бэкенд, расположенный на 5000 порту. CORS не возникает, потому что для браузера Origin совпадает. Мы перекладываем JSON из кармана в карман.
Итог: что нужно запомнить
- CORS — браузерная, а не серверная защита
- Если возникает ошибка CORS, начинать копать нужно с настроек бэкенда
- Для разработки проще всего настроить прокси на дев-сервере, чтобы фронтенд и бэкенд имели одинаковый Origin
Мы не в Хогвартсе, поэтому никакой магии тут нет. Желаю успехов!







