Каждый разработчик периодически сталкивается с огромной красной строкой в консоли — Access to fetched has been blocked by CORS policy. Да, это здорово расстраивает. И хотя есть способы быстро избавиться от этой ошибки, давайте сегодня не будем принимать их как должное — лучше разберёмся, что на самом деле делает CORS и почему эта технология на самом деле наш друг.

В этой статье я не буду останавливаться на основах HTTP. Скажу лишь, что в своих примерах я использую HTTP/1.1, а не HTTP/2 — на CORS это никак не влияет.

Во фронтенде часто требуется отобразить данные, которые хранятся в другом месте. Перед этим браузер должен направить запрос серверу: клиент отправляет HTTP-запрос со всей информацией, которая нужна серверу, чтобы вернуть данные.

Представим, что нам надо получить информацию о пользователях для нашего сайта www.mywebsite.com с сервера, который находится по адресу api.website.com.

Сработало. Мы отправили HTTP-запрос на сервер, который вернул нужные нам данные в формате JSON. А теперь давайте попытаемся отправить точно такой же запрос, но с другого домена: вместо www.mywebsite.com возьмём www.anotherdomain.com.

Что произошло? Мы отправили такой же запрос, но на этот раз браузер выдал странную ошибку. Мы только что увидели CORS в действии. Давайте разберёмся, почему возникла эта ошибка, и что она означает.

Правило одинакового источника (Same-Origin Policy)

В веб внедрено так называемое правило одинакового источника. По умолчанию мы можем получить доступ к ресурсам только в том случае, если источник этих ресурсов и источник запроса совпадают. К примеру, мы сможем без проблем загрузить изображение, которое находится по адресу https://mywebsite.com/image1.png.

Ресурс считается принадлежащим к другому источнику (cross-origin), если он располагается на другом домене/поддомене, протоколе или порте.

Это, конечно, здорово, но для чего правило одинакового источника вообще существует?

Представим, что это правило не работает, а вы случайно нажали на какую-то вирусную ссылку, которую прислала ваша тётушка на Фейсбуке. Ссылка перенаправляет вас на мошеннический сайт, который с помощью фрейма загружает интерфейс сайта вашего банка и успешно залогинивает вас с помощью сохранённых куки.

Разработчики этого мошеннического сайта сделали так, чтобы он имел доступ к фрейму и мог взаимодействовать с DOM сайта вашего банка — так они смогут переводить деньги на свой счёт от вашего имени.

Да, это огромная угроза безопасности  —  мы ведь не хотим, чтобы кто угодно имел доступ к чему угодно.

К счастью, здесь приходит на помощь правило одинакового источника: оно гарантирует, что мы можем получить доступ только к ресурсам из того же самого источника.

В данном случае сайт www.evilwebsite.com попытался получить доступ к ресурсам из другого источника —www.bank.com. Правило одинакового источника заблокировало это действие. В результате разработчики мошеннического сайта не смогли добраться до нашей банковской информации.

Но какое отношение всё это имеет к CORS? 

CORS на стороне клиента

Несмотря на то, что правило одинакового источника применяется исключительно к скриптам, браузеры распространили его и на JavaScript-запросы: по умолчанию можно получить доступ к ресурсам только из одинакового источника.

Но нам ведь часто нужно обращаться к ресурсам из других источников… Может, тогда фронтенду стоит взаимодействовать с API на бэкенде, чтобы загружать данные? Чтобы обеспечить безопасность запросов к другим источникам, браузеры используют механизм под названием CORS.

Аббревиатура CORS расшифровывается как Cross-Origin Resource Sharing (Технология совместного использования ресурсов между разными источниками). Несмотря на то, что браузеры не позволяют получать доступ к ресурсам из разных источников, можно использовать CORS, чтобы внести небольшие коррективы в эти ограничения и при этом быть уверенным, что доступ будет безопасным.

Пользовательские агенты (к примеру, браузеры) на основе значений определённых заголовков для CORS в HTTP-запросе могут проводить запросы к другим источникам, которые без CORS были бы заблокированы.

Когда происходит запрос к другому источнику, клиент автоматически подставляет дополнительный заголовок Origin в HTTP-запрос. Значение этого заголовка отражает источник запроса.

Чтобы браузер разрешил доступ к ресурсам из другого источника, он должен получить определённые заголовки в ответе от сервера, которые указывают, разрешает ли сервер запросы из других источников.

CORS на стороне сервера

Разрабатывая бэкенд, мы, чтобы разрешить запросы из других источников, можем добавить в HTTP-ответ дополнительные заголовки, начинающиеся с Access-Control-*. На основе значений этих CORS-заголовков браузер сможет разрешить определённые запросы из других источников, которые обычно блокируются правилом одинакового источника.

Существует несколько CORS-заголовков, но браузеру нужен всего один из них, чтобы разрешить доступ к ресурсам из разных источников —Access-Control-Allow-Origin. Его значение определяет, из каких источников можно получить доступ к ресурсам на сервере.

Если мы создаём сервер, к которому должен иметь доступ сайт https://mywebsite.com, то нужно внести этот домен в значение заголовка Access-Control-Allow-Origin.

Allowed origins — разрешённые источники

Отлично, теперь мы можем получать ресурсы из другого источника. А что будет, если мы попытаемся получить к ним доступ из источника, который не указан в заголовке Access-Control-Allow-Origin?

Allowed Origins - Разрешённые источники
The request has an allowed 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.mywebsite.com' заголовка 'Access-Control-Allow-Origin' не совпадает с представленным источником)

В данном случае в качестве источника выступал сайт https://www.anotherwebsite.com— его не было в списке разрешённых источников в заголовке Access-Control-Allow-Origin. CORS успешно заблокировал запрос, и мы не можем получить доступ к запрашиваемым данным.

В качестве значения разрешённых источников CORS позволяет указать спецсимвол *. Он означает, что доступ к ресурсам открыт из всех источников, поэтому используйте его с осторожностью.

Кроме Access-Control-Allow-Origin, мы можем использовать и многие другие CORS-заголовки. Бэкенд-разработчик может изменить правила CORS на сервере так, чтобы разрешать/блокировать определённые запросы.

Ещё один довольно распространённый заголовок — Access-Control-Allow-Methods. С ним будут разрешены только те запросы из других источников, которые выполнены с применением перечисленных методов.

Allowed Origins - Разрешённые источники
Allowed Methods - Разрешённые методы

В данном случае разрешены только запросы с методами GET, POST, или PUT. Запросы с другими методами (например, PATCH или DELETE) будут блокироваться.

Если вам интересно почитать о других CORS-заголовках, ознакомьтесь с их списком на MDN.

С PUT, PATCH и DELETE CORS работает с по-другому. В этих “непростых” случаях используются так называемые предварительные запросы (preflight requests).

Предварительные запросы

Существует два типа CORS-запросов: простые и предварительные. Тип запроса зависит от хранящихся в нём значений (не волнуйтесь, здесь не надо будет ничего запоминать).

Запрос считается простым, если в нём используются методы GET и POST и нет никаких пользовательских заголовков. Любые другие запросы (например, с методами PUT, PATCH или DELETE) — предварительные.

Если интересно узнать, каким требованиям должен соответствовать запрос, чтобы называться простым, почитайте эту статью на MDN.

Но что означают и почему существуют “предварительные запросы”?

Перед отправкой текущего запроса клиент сначала генерирует предварительный запрос: в своих заголовках Access-Control-Request-* он содержит информацию о текущем запросе. Это позволяет серверу узнать метод, дополнительные заголовки и другие параметры запроса, который браузер пытается отправить.

Actual Request — Текущий запрос
Preflighted Request — Предварительный запрос

Сервер получает этот предварительный запрос и отправляет обратно пустой HTTP-ответ с CORS-заголовками сервера. Браузер в свою очередь получает предварительный ответ (только CORS-заголовки) и проверяет, разрешён ли HTTP-запрос. 


The request has an allowed origin - Источник запроса разрешён
The request has an allowed method - Метод запроса разрешён
Preflight Response - Предварительный ответ

Если всё в порядке, браузер посылает текущий запрос на сервер, а тот в ответ присылает данные, которые мы запрашивали.

Если же возникает проблема, CORS блокирует предварительный запрос, а текущий вообще уже не будет отправлен. Предварительный запрос — отличный способ уберечь нас от получения доступа или изменения ресурсов на серверах, у которых (пока что) не настроены правила CORS. Сервера защищены от потенциально нежелательных запросов из других источников.

Чтобы уменьшить число обращений к серверу, можно кэшировать предварительные ответы, добавив к CORS-запросам заголовок Access-Control-Max-Age. Так браузеру не придётся каждый раз отправлять новый предварительный запрос.

Учётные данные

Куки, заголовки авторизации, TLS-сертификаты по умолчанию включены только в запросы из одинакового источника. Однако нам может понадобиться использовать учётные данные и в запросах из разных источников. Возможно, мы захотим включить куки в запрос, который сервер сможет использовать для идентификации пользователя.

В CORS по умолчанию отсутствуют учётные данные, но это можно изменить, добавив CORS-заголовок Access-Control-Allow-Credentials.

Если необходимо включить куки и другие заголовки авторизации в запрос из другого источника, нужно установить значение true в поле withCredentials запроса, а также добавить в ответ заголовок Access-Control-Allow-Credentials.

Готово  —  теперь мы можем включать учётные данные в запрос из другого источника.

Думаю, мы все согласимся с тем, что появление ошибок CORS порой расстраивает, но, тем не менее, здорово, что CORS позволяет безопасно отправлять запросы из разных источников в браузере — считаю, что мы должны любить эту технологию чуточку сильнее 🙂

Разумеется, о правиле одинакового источника и CORS можно рассказать гораздо больше, но я просто не смогу уместить всё это в одной статье. К счастью, ресурсов много (к примеру, спецификация W3)  —  вам будет к чему обратиться, если захотите подробнее изучить эту тему.

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Lydia Hallie: CS Visualized: CORS

Предыдущая статьяJava. Вложенные классы
Следующая статьяАнимируем скучные табличные представления в iOS-приложении