Fetch и CORS. Пример на ReactJS и другие

Fetch and CORS. React JS examples and etc. Fetch и кросс-доменные запросы. Пример на React JS и т.д.

Йо-йо! Недавно я столкнулся с задачей — создать сайт на котором отображаются бонусы клиента. Бонусы я получаю с web-сервиса из 1С, он отдаёт мне json с данными, но предварительно там я должен авторизоваться.

Для создания этого сайта я решил использовать reactjs. Сайт довольно простой и многого там не будет, но нужно сделать авторизацию. Авторизация происходила с помощью передачи заголовка Authorization примерно вот так:

Authorization: Basic 0ZHJadMU0KI6

Но как только я сделать fetch и запросить данные я столкнулся с множеством проблем в том числе CORS. Я решил эти проблемы и теперь хочу поделиться своим опытом.

Fetch и авторизация

Чтобы fetch мог передавать данные для авторизации нужно явно «сказать» ему, что в нём передаются данные для авторизации с помощью credentials. Пример

// Умышленно опускаю прочий код компонента, сейчас не важно.
componentDidMount(){
    const url = 'https://172.168.0.1/api/v1/methdeName?id="123213"';
    const headers = new Headers({
        'Authorization': '0ZHJadMU0KI6' 
    });
    const options = {
        headers,
        credentials:"include" // Вот, что нужно задать
    };
    fetch(url, options).then((response)=>{
        console.log(response.json());
    })
}

В этом случае всё пройдёт гладко и вы успешно пройдёте авторизацию.

Fetch и CORS

CORS сильно портит жизнь веб-мастеру, но зато защищает нас. Чтобы описать описать взаимодействие fecth и сервера в корсcдоменных запросах я приведу несколько примеров из спецификации fetch.

Пример 1

Скрипт на https: //foo.invalid/ хочет получить некоторые данные с https: //bar.invalid/. (Ни учетные данные, ни доступ к заголовку ответа не важны.)

var url = "https://bar.invalid/api?key=730d67a37d7f3d802e96396d00280768773813fbe726d116944d814422fc1a45&data=about:unicorn";
fetch(url).then(success, failure)

При этом будет использоваться протокол CORS, хотя он полностью прозрачен для разработчика из foo.invalid. Как часть протокола CORS, пользовательский агент будет включать заголовок Origin в запрос:

Origin: https://foo.invalid

Получив ответ от bar.invalid, пользовательский агент проверит заголовок ответа «Access-Control-Allow-Origin». Если его значение равно https: // foo.invalid или *, пользовательский агент вызовет успешный обратный (success) вызов. Если оно имеет какое-либо другое значение или отсутствует, пользовательский агент вызовет failure.

Пример 2

Разработчик foo.invalid вернулся и теперь хочет получить некоторые данные из bar.invalid, одновременно обращаясь к заголовку ответа.

fetch(url).then(response => {
  var hsts = response.headers.get("strict-transport-security"),
      csp = response.headers.get("content-security-policy")
  log(hsts, csp)
})

bar.invalid предоставляет правильный заголовок ответа Access-Control-Allow-Origin в соответствии с предыдущим примером. Значения hsts и csp будут зависеть от заголовка ответа «Access-Control-Expose-Headers». Например, если в ответ включены следующие заголовки

Content-Security-Policy: default-src 'self'
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
Access-Control-Expose-Headers: Content-Security-Policy

тогда hsts будет нулевым, а csp будет «default-src ‘self», даже если ответ включает оба заголовка. Это связано с тем, что bar.invalid должен явно разделять каждый заголовок, перечисляя их имена в заголовке ответа Access-Control-Expose-Headers.

В качестве альтернативы, если bar.invalid хочет совместно использовать все свои заголовки ответа, для запросов, которые не включают учетные данные, он может использовать ‘*’ в качестве значения для заголовка ответа Access-Control-Expose-Headers. Если бы запрос включал учетные данные, имена заголовков ответов должны были бы быть перечислены явно, и ‘*’ не мог бы использоваться.

Пример 3. С передачей учётных данных (пароля)

Разработчик foo.invalid извлекает некоторые данные из bar.invalid, включая учетные данные. На этот раз протокол CORS больше не прозрачен для разработчика, поскольку учетные данные требуют явного согласия:

fetch(url, { credentials:"include" }).then(success, failure)

Это также делает все заголовки ответа Set-Cookie bar.invalid полностью функциональными (в противном случае они игнорируются).

Пользовательский агент обязательно включит в запрос все соответствующие учетные данные. Это также повысит требования к ответу. Мало того, что bar.invalid нужно будет перечислить https: // foo.invalid в качестве значения для заголовка Access-Control-Allow-Origin (‘*’ не допускается, когда задействованы учетные данные), Access-Control-Allow-Credentials заголовок также должен присутствовать:

Access-Control-Allow-Origin: https://foo.invalid
Access-Control-Allow-Credentials: true

Если ответ не включает эти два заголовка с этими значениями, будет вызван failure callback (в fetch). Однако любые заголовки ответа Set-Cookie будут соблюдены.

CORS, Fetch и сервер

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

// Нет авторизации
Access-Control-Allow-Origin: *
// Нужна авторизация, помним, что обызателен https на сервере и клиенте
Access-Control-Allow-Origin: https://example.ru 
Access-Control-Allow-Credentials: true
// Нужен доступ к заголовкам ответа
Access-Control-Expose-Headers: * 

HTTPS для ReactJS во время разработки

Я использовал статью с medium, она довольно простая.

P.S.

Ссылка на оригинальные примеры https://fetch.spec.whatwg.org/#example-cors-with-credentials

Если вы не понимаете, что такое fetch предлагаю прочитать мою статью «fetch в reactjs«, возможно так же вам понадобиться статья про router в react

Надеюсь я сэкономил вам немного времени, ведь именно это основная цель моего сайта. Если вы хотите и дальше экономить своё время то обязательно подписывайтесь на обновления сайта с помощью push-уведомлений.