Если вы когда-нибудь писали фронтенд, который обращается к API, скорее всего, сталкивались с загадочной ошибкой:

Access to fetch at 'https://api.example.com/data' from origin 'http://localhost:3000' has been blocked by CORS policy...

Многие просто добавляют "какой-нибудь заголовок" или ставят прокси - но давайте разберёмся по существу, что такое CORS, зачем он нужен и как правильно с ним работать.


Что такое CORS

CORS (Cross-Origin Resource Sharing) - это механизм безопасности в браузерах, который регулирует доступ веб-страницы к ресурсам с другого домена (origin).

Origin - это комбинация протокола + домена + порта.

Например:

  • https://example.com и https://api.example.com - разные origin-ы
  • http://localhost:3000 и http://localhost:8080 - тоже разные
  • но https://example.com/page и https://example.com/api - один и тот же origin.

CORS - не библиотека, не протокол, не плагин. Это набор HTTP-заголовков, с помощью которых сервер говорит браузеру, какие источники могут обращаться к его ресурсам.


Проблема, которую решает CORS

Без CORS, любой сайт мог бы делать AJAX-запросы на другой сайт от вашего имени, красть токены и данные, менять настройки и т.д.

Например:

// Пользователь залогинен в bank.com
fetch('https://bank.com/transfer', {
    method: 'POST',
    body: JSON.stringify({ to: 'attacker', amount: 10000 })
});

Если бы браузер не проверял источник - злоумышленник мог бы разместить этот код где угодно, и запрос отправился бы с вашими cookie.

Именно поэтому браузеры блокируют такие запросы без разрешения сервера - и это называется CORS-политика.


Как работает CORS

Когда фронтенд делает запрос на другой origin, браузер добавляет специальные заголовки и ждёт ответа от сервера, который должен подтвердить, что этот запрос разрешён.

- Простой запрос (Simple Request)

К таким запросам относятся только:

  • методы: GET, POST, HEAD
  • заголовки: Accept, Content-Type (только text/plain, application/x-www-form-urlencoded, multipart/form-data)

Пример:

fetch('https://api.example.com/users', {
    method: 'GET'
});

Браузер просто отправит запрос, и если сервер вернёт заголовок:

Access-Control-Allow-Origin: https://myfrontend.com

запрос будет разрешён.

Если сервер не вернул этот заголовок, браузер не даст JavaScript-у доступ к ответу, хотя сам запрос на сервер уйдёт.

- Предварительный запрос (Preflight Request)

Если запрос не простой (например, метод PUT, DELETE или есть кастомные заголовки), браузер сначала делает "предварительный" OPTIONS-запрос:

OPTIONS /users HTTP/1.1
Origin: https://myfrontend.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization

Сервер должен ответить:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://myfrontend.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 600

Если браузер получает "OK" - он выполняет основной запрос.


Важные заголовки CORS

Access-Control-Allow-Origin. Разрешённые источники. Можно указать конкретный домен (https://site.com) или * для всех.

Access-Control-Allow-Methods. Какие HTTP-методы разрешены (GET, POST, PUT, DELETE).

Access-Control-Allow-Headers. Разрешённые кастомные заголовки (Authorization, X-Requested-With, и т.д.).

Access-Control-Allow-Credentials. Если true, позволяет передавать cookie и заголовки авторизации.

Access-Control-Expose-Headers. Разрешённые для чтения заголовки ответа.

Access-Control-Max-Age. Время кеширования результата preflight-запроса.


CORS - это не "баг", а фундамент браузерной безопасности.

Он защищает пользователей от того, чтобы злоумышленники не могли выполнять запросы от их имени к сторонним сервисам. Поняв, как он работает, вы перестанете "ставить костыли" и сможете грамотно проектировать клиент-серверное взаимодействие.


Source: Orkhan Alishov's notes