
Доброго времени суток, друзья!
Представляю вашему вниманию перевод статьи «CS Visualized: CORS» автора Lydia Hallie.
Каждому разработчику приходилось сталкиваться с ошибкой
Access to fetched has been blocked by CORS policy
. Существует несколько способов быстрого решения данной проблемы. Однако, давайте не будем спешить и подробно рассмотрим, что из себя представляет политика CORS. У нас часто возникает необходимость отобразить данные, находящиеся в другом месте. Прежде чем мы сможем это сделать, браузер должен отправить запрос на сервер, чтобы получить эти данные.
Допустим, мы хотим получить информацию о пользователе на нашем сайте
www.mywebsite.com
с сервера, расположенного на сайте api.website.com
. 
Отлично! Мы только что отправили запрос на сервер и получили в ответ данные в формате JSON.
Теперь попробуем отправить аналогичный запрос на другой домен. Вместо того, чтобы отправлять запрос с
www.mywebsite.com
, отправим его с www.anotherdomain.com
. 
Что случилось? Мы отправили точно такой же запрос, но на этот раз браузер показывает какую-то ошибку.
Мы наблюдаем CORS в действии. Почему возникла данная ошибка и что она означает?
Политика общего происхождения
В вебе присутствует нечто под названием политика общего происхождения (далее — ПОП). По умолчанию мы имеем доступ только к тем ресурсам, которые находятся в том же источнике, что и источник нашего запроса. Например, мы можем загрузить изображение, находящееся в
https://mywebsite.com/image1.png
. Источник является другим, когда он расположен в другом (под)домене, протоколе или порте.

Круто, но зачем нужна ПОП?
Предположим, что ее не существует, и вы случайно кликаете по вирусной ссылке, которую прислала ваша тетя в Facebook. Данная ссылка перенаправляет вас на «вредоносный сайт», имеющий встроенный iframe, который загружает сайт вашего банка и успешно авторизуется там с помощью куки.
Разработчики «злого сайта» позаботились о том, чтобы он имел доступ к iframe и мог взаимодействовать с содержимым DOM сайта вашего банка для перечисления денежных средств на свой аккаунт от вашего имени.

Да уж… это серьезная проблема безопасности. Мы не хотим, чтобы кто-нибудь имел доступ к чему-либо без нашего ведома.
К счастью, существует ПОП. Эта политика ограничивает доступ к ресурсам из других источников.

В данном случае источник
www.evilwebsite.com
пытается получить доступ к ресурсу из источника www.bank.com
. ПОП блокирует такой доступ и запрещает разработчикам «плохого сайта» доступ к вашим банковским данным. Хорошо, но… как это работает?
CORS на стороне клиента
Несмотря на то, что ПОП применяется только к скриптам, браузеры «расширяют» ее до любых JavaScript-запросов: по умолчанию мы имеем доступ только к ресурсам из одного источника.

Хм, но… у нас часто возникает необходимость получить ресурсы из другого источника. Возможно, нашему фронтенду нужно обратиться к серверному прикладному интерфейсу для загрузки данных. Для безопасного получения ресурсов из других источников браузеры реализуют механизм под названием CORS.
CORS расшифровывается как Cross-Origin Resource Sharing (совместное использование ресурсов). Хотя браузеры запрещают получение ресурсов из других источников, мы можем использовать CORS для изменения этого ограничения, оставаясь при этом в безопасности.
Пользовательские агенты (браузеры) могут использовать механизм CORS для разрешения запросов между разными источниками, которые в противном случае были бы заблокированы, на основе некоторых заголовков HTTP-ответа.
Когда происходит запрос к другому источнику, клиент автоматически добавляет в HTTP-запрос заголовок
Origin
. Значением этого заголовка является источник запроса. 
Для того, чтобы браузер разрешил получение ресурсов из другого источника, в ответе сервера также должен содержаться определенный заголовок.
CORS на стороне сервера
Как разработчики серверной части приложения, мы можем разрешить получение наших ресурсов другими источниками посредством включения в ответ специальных заголовков, начинающихся с
Access-Control-*
. На основе значения таких заголовков браузер разрешает совместное использование ресурсов. Существует несколько CORS-заголовков, но один из них является обязательным:
Access-Control-Allow-Origin
. Значение данного заголовка определяет, какие источники могут получать наши ресурсы.
Если мы разрабатываем сервер, который должен быть доступен
https://mywebsite.com
, мы должны добавить этот домен в заголовок Access-Control-Allow-Origin
. 
Здорово. Данный заголовок теперь добавляется к ответу сервера, отправляемого клиенту. ПОП больше не запрещает нам получать ресурсы с
https://api.mywebsite.com
с помощью запросов, отправленных с https://mywebsite.com
. 
Механизм CORS, реализованный в браузере, проверяет совпадение значений заголовка ответа
Access-Control-Allow-Origin
и заголовка запроса Origin
. В данном случае источником нашего запроса является
https://www.mywebsite.com
, указанный в списке заголовка ответа Access-Control-Allow-Origin
. 
Отлично. Теперь мы можем получать ресурсы из других источников. Что произойдет, если мы попытаемся сделать это из источника, не указанного в
Access-Control-Allow-Origin
? 
Да, CORS заблокировал доступ к ресурсам.
The 'Access-Control-Allow-Origin' header has a value
'https://www.mywebsite.com' that is not equal
to the supplied origin.
В данном случае источником является
https://www.anotherwebsite.com
, не указанный в Access-Control-Allow-Origin
. CORS успешно запретил получение запрашиваемых данных. CORS позволяет указать
*
в качестве значения разрешенных источников. Это означает, что ресурсы будут доступны любым источникам, так что будьте осторожны. Access-Control-Allow-Origin
— это один из многих заголовков, которые мы можем установить. Разработчик серверной части может настраивать CORS для разрешения (запрета) конкретных запросов. Другим распространенным заголовком является
Access-Control-Allow-Methods
. CORS разрешает только те запросы из других источников, которые были отправлены с помощью указанных методов. 
В данном случае разрешены только запросы, отправленные с помощью методов GET, POST или PUT. Другие методы, такие как PATCH или DELETE будут заблокированы.
Говоря о запросах, отправленных с помощью методов PUT, PATCH и DELETE, CORS обрабатывает их особым образом. Эти «непростые» запросы иногда называют предварительными (preflight).
Предварительные запросы
CORS работает с двумя типами запросов: простыми и предварительными. То, каким является запрос, зависит от некоторых его значений.
Запрос является простым, если он отправлен с помощью методов GET или POST и не содержит дополнительных заголовков. Любой другой запрос является предварительным.
Хорошо, но что означает предварительный запрос и зачем нужны такие запросы?
Перед отправкой фактического запроса, клиент направляет серверу предварительный запрос с информацией о фактическом запросе: о его методе, дополнительных заголовках, включая
Access-Control-Request-*
и т.д. 
Сервер получает предварительный запрос и отправляет пустой предварительный ответ, содержащий CORS-заголовки. Браузер получает предварительный ответ и проверяет, будет ли разрешен фактический запрос.

Если да, то браузер отправляет фактический запрос и получает данные в ответ.

Если нет, CORS заблокирует предварительный запрос и фактический запрос не будет отправлен. Предварительные запросы — это отличный способ предотвратить доступ и изменение ресурсов на сервере. Это защищает сервер от потенциально нежелательных запросов из других источников.
Для уменьшения количества повторных запросов мы можем закэшировать предварительный ответ посредством добавления заголовка
Access-Control-Max-Age
в CORS-запрос. Это позволяет избежать повторного направления предварительного запроса. Полномочия (credentials)
Куки, заголовки авторизации и сертификаты TLS по умолчанию устанавливаются только для запросов из одного источника. Однако у нас может возникнуть необходимость использовать эти полномочия в запросе из другого источника. Возможно, мы хотим включить в запрос куки, которые сервер может использовать для идентификации пользователя.
Хотя CORS не содержит полномочий по умолчанию, мы можем изменить это с помощью заголовка
Access-Control-Allow-Credentials
. Если мы хотим включить куки и другие заголовки авторизации в наш запрос из другого источника, нам нужно присвоить полю
withCredentials
значение true
в запросе и добавить заголовок Access-Control-Allow-Credentials
в ответ. 
Готово, теперь мы можем включать полномочия в наши запросы из другого источника.
Надеюсь, статья была вам полезной. Благодарю за внимание.