Если вы видите эту ошибку — вы не одиноки:
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) https://www.w3.org/TR/cors/
MDN Web Docs: Cross-Origin Resource Sharing (CORS)
https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
Браузерная безопасность (Same-Origin Policy)
Подробное объяснение политики одинакового источника
https://web.dev/articles/same-origin-policy
CORS для разработчиков (Google Web Fundamentals)
Практические сценарии настройки с примерами кода
https://web.dev/articles/cross-origin-resource-sharing
Готовлю на мидл+/сеньора. Собираю материал для подготовки к собеседованию и делаю простым для понимания. Хочу сделать цикл статей, если у вас есть темы трудные для понимания - пишите, по возможности помогу.
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 он для защиты только от этой беды с кукисами, или там есть и другие сценарии атаки?
movl
У куки есть SameSite атрибут, который позволяет настраивать, какие куки могут отправляться с запросом, исходя из источника запроса. Но это сильно позже появилось чем Access-Control-Allow-Origin, и не является заменой, скорее еще одна из мер защиты.