Если вы видите эту ошибку — вы не одиноки:
Access to fetch at 'https://api.site.com' from origin 'http://localhost:3000'
has been blocked by CORS policy.
Разберем, почему это происходит, и как это починить. Что такое CORS, и для чего он нужен.
1. Зачем нужен CORS? Безопасность!
Пример атаки:
Вы вошли на bank.com. Данные для авторизации сохранились в куках.
Заходите на evil.com.
Сайт evil.com тайно отправляет запрос Get /api/account на bank.com.
Браузер автоматически отправляет куки к запросу на bank.com → злоумышленник получает данные счета и, возможно, списывает деньги.
2. Что такое CORS?
CORS (Cross-Origin Resource Sharing) - русского обозначения не имеет. Дословно "межисточниковый" обмен ресурсами.
Цель браузера: защитить пользователя от вредоносных сайтов за счет блокировки запросов к неразрешенным ресурсам.
-
Как работает:
Браузер выполняет вызов ресурса.
Получает заголовки Access-Control.
Проверяет разрешенные заголовки на соответствие домена и запроса.
Блокирует или разрешает чтение результата запроса.
Пример: пользователь открывает сайт evil.com. Если bank.com настроил CORS, JavaScript на evil.com не сможет прочитать ответ от bank.com/api/account.
CORS не защищает от кросс-доменных запросов (CSRF-атак). Браузер проверяет заголовки после получения ответа, блокируя передачу ответа в js код.
3. Заголовки CORS
# Разрешённые домены (один, список или *)
Access-Control-Allow-Origin: https://frontend.com
# Разрешённые HTTP-методы (список или *)
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
# Разрешённые заголовки для отправки(предварительный запрос)
Access-Control-Allow-Headers: Authorization, Content-Type, X-Requested-With
# Разрешённые для чтения заголовки (основной запрос)
Access-Control-Expose-Headers: Authorization, Content-Type, X-Requested-With
# Разрешить передачу кук/токенов
Access-Control-Allow-Credentials: true
# Кэшировать предварительный запрос на 600 сек (10 мин)
Access-Control-Max-Age: 600
4. Предзапросы (Preflight-запросы)
"Простые запросы" (GET, POST, HEAD без спец. заголовков) отправляются сразу.
"Сложные запросы", например, с методами PUT, DELETE или с нестандартными заголовками, сначала отправляют "предзапрос" (preflight request) методом OPTIONS.
Сервер должен ответить, разрешены ли такие запросы.
Например, перед вызовом GET с заголовком X-API-Key будет выполнен запрос:
http
/data HTTP/1.1
Origin:
https://frontend.com
Access-Control-Request-Headers: X-API-Key
Сервер должен ответить:
http
HTTP/1.1 204 OK
Access-Control-Allow-Origin:
https://frontend.com
Access-Control-Allow-Headers: X-API-Key
И только потом отправится основной запрос.
5. Что считается "разными источниками"?
При настройке CORS браузеры блокируют запросы руководствуясь "Политикой одинакового источника" или Same-Origin Policy (SOP).
Источник определяется тремя параметрами:
Протокол (http/https)
Домен (site.com/api.site.com)
Порт (:80/:3000)
✅ Одинаковые источники:
https://site.com/page и https://site.com/about (отличаются путем или аргументами)
❌ Разные источники:
http://site.com и https://site.com (разный протокол)
https://site.com и https://api.site.com (разный домен)
6. Как работает CORS на практике?
1. Простые запросы (GET/POST без спец. заголовков)
Браузер разрешает их, но не даст прочитать ответ, если сервер не добавит в ответ:
http
Access-Control-Allow-Origin:
https://your-frontend.com
2. Сложные запросы (PUT, DELETE, кастомные заголовки)
Браузер сначала отправляет предзапрос (OPTIONS). Сервер должен ответить:
http
Access-Control-Allow-Origin:
https://your-frontend.com
Access-Control-Allow-Methods: POST, DELETE
Access-Control-Allow-Headers: Content-Type
→ Затем браузер отправит основной запрос.
7. Пример для Express.js:
// Разрешить запросы с frontend.com
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', 'https://frontend.com');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
next();
});
8. Лучшие практики CORS
1.Не используйте * для защищенных данных
Разрешайте только доверенные домены:
Access-Control-Allow-Origin:
https://your-frontend.com
2.Для публичных API можно использовать *:
Access-Control-Allow-Origin: *
3.Куки = осторожно!
Если используете куки:
http
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin:
https://frontend.com
// Нельзя использовать *
4.Тестируйте предзапросы:
Для PUT, DELETE и запросов с Authorization всегда настраивайте обработку OPTIONS.
Используйте Access-Control-Max-Age чтобы снизить нагрузку
Настройте веб-сервер (Nginx/Apache) для оптимизации, обработки OPTIONS без запуска приложения
9. Частые ошибки
1. Забыли добавить заголовки на сервере → Браузер блокирует ответ.
2. Использовали * с куками → CORS ошибка.
3. Не настроили OPTIONS для сложных запросов → Предзапрос проваливается.
10. Итог:
CORS — защищает пользователя браузера от получения его данных вредоносными сайтами. Настраивать нужно
? Проверка: Откройте вкладку Network в DevTools. Ищите статусы OPTIONS и CORS headers в ответах сервера.
11. Что почитать:
W3C Cross-Origin Resource Sharing
Оригинальная спецификация CORS (2014)
MDN Web Docs: Cross-Origin Resource Sharing (CORS)
Браузерная безопасность (Same-Origin Policy)
Подробное объяснение политики одинакового источника
CORS для разработчиков (Google Web Fundamentals)
Практические сценарии настройки с примерами кода
Готовлю на мидл+/сеньора. Собираю материал для подготовки к собеседованию и делаю простым для понимания. Хочу сделать цикл статей, если у вас есть темы трудные для понимания — пишите, по возможности помогу.
Комментарии (10)
FanatPHP
09.08.2025 14:51С первых же строк видно непонимание темы самим автором.
3.Код, полученный с сайта evil.com, заставляет ваш браузер тайно отправить запрос Get /api/account на bank.com.
Ну и все признаки текста, сгенерированного ИИ. Печально.
maslyaev
Наивный вопрос по примеру атаки. Если пользователь сидит на странице evil.com, то почему браузер отправляет куки, полученные на странице bank.com? Может быть, изначально беда именно в этом?
Alexandroppolus
Куки можно не отсылать (как и делается в том же fetch без
credentials: "include"
), но этого недостаточно. Пользователь может находиться во внутренней сети и иметь доступ к каким-то ресурсам, недоступным снаружи, сайт злоумышленника не должен уметь забирать оттуда какие-либо данные, даже без кукисов.maslyaev
То есть fetch без чужих куков пойдёт только если JS на сайте evil.com написан честным человеком? Чё-то ржу.
Правильно ли я понимаю, что весь этот "горячо любимый" всеми веб-разработчиками CORS это кривой костыль, которым попытались пофиксить этот косяк?
Alexandroppolus
fetch без чужих куков пойдёт, если не указать
credentials: "include"
. Иначе пойдет с куками. Но в любом случае ответ сервера будет передан запросившему его js-коду, только если сервер снабдил свой ответ необходимыми заголовками (причем для запроса с куками заголовков должно быть больше). От честности автора тут ничего не зависит.Я бы не назвал это костылем. Просто изначально были ограничения на "одинаковый origin", а спустя несколько лет добавились цивилизованные средства их обхода с согласия сервера. До этого был JSONP, который действительно костыль.
maslyaev
Но ведь человек с evil.com не забудет прописать
credentials: "include"
, правда ведь?Ещё один вопрос. CORS он для защиты только от этой беды с кукисами, или там есть и другие сценарии атаки?
Dmitri-D
чтобы куки пошли, надо чтобы и bank разрешил и evil прописал. Одного желания evil не достаточно. Bank - или запрещает все поползновения не от bank, или разрешает только явно тем, кому он хочет разрешить.
gluck59
Такие костыли разной степени кривости есть не только в ИТ.
Например в интернет-банкинге проверка по протоколу 3DS, так горячо рекламируемая банками, будет работать только если продавец включил ее у себя в магазине.
Да — не банк, не покупатель, а продавец.
Таким образом если продавец мошенник, вся эта любовно нагороженная система безопасности даже не включится...
Скрытый текст
movl
У куки есть SameSite атрибут, который позволяет настраивать, какие куки могут отправляться с запросом, исходя из источника запроса. Но это сильно позже появилось чем Access-Control-Allow-Origin, и не является заменой, скорее еще одна из мер защиты.
CuBeR_HeMuL
Например, на сайте comments-from-vk.com выводится блок с комментариями пользователя из ВК (или с возможностью оставить комментарий от лица пользователя ВК). В таком случае куки необходимы, чтобы ВК мог отдать данные о профиле пользователя, например, или чтобы comments-from-vk.com знал, какой пользователь ВК оставил комментарий