Йо-йо! Недавно я столкнулся с задачей — создать сайт, на котором отображаются бонусы клиента. Бонусы я получаю с web-сервиса из 1С, он отдаёт мне json с данными, но предварительно там я должен авторизоваться.
Для создания этого сайта я решил использовать reactjs. Сайт довольно простой и многого там не будет, но нужно сделать авторизацию. Авторизация происходила с помощью передачи заголовка Authorization примерно вот так:
Authorization: Basic 0ZHJadMU0KI6
Но как только я решил сделать fetch и запросить данные, столкнулся со множеством проблем, в том числе CORS. Я решил эти проблемы и теперь хочу поделиться своим опытом.
Чтобы 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()); }) }
В этом случае всё пройдёт гладко и вы успешно пройдёте авторизацию.
CORS сильно портит жизнь веб-мастеру, но зато защищает нас. Чтобы описать взаимодействие fecth и сервера в корсcдоменных запросах я приведу несколько примеров из спецификации fetch.
Скрипт на 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.
Разработчик 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. Если бы запрос включал учетные данные, имена заголовков ответов должны были бы быть перечислены явно, и ‘*’ не мог бы использоваться.
Разработчик 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 будут соблюдены.
Как мы уже поняли, нам нужно задавать правильные заголовки для того, чтобы кросс-доменные запросы работали и мы имели доступ к контенту, который присылает сервер. Вот выжимка заголовков:
// Нет авторизации Access-Control-Allow-Origin: * // Нужна авторизация, помним, что обызателен https на сервере и клиенте Access-Control-Allow-Origin: https://example.ru Access-Control-Allow-Credentials: true // Нужен доступ к заголовкам ответа Access-Control-Expose-Headers: *
Я использовал статью с medium, она довольно простая.
Во время кросс-доменных запросов get-запрос преобразуется в option в том случае, если вы передаёте заголовки и в fetch’е установлено {credentials:»include»}. В таком случае появляется ошибка » 405 method not allowed».
Options-запрос отправляется для того, чтобы браузер понял можно ли вообще передавать заголовки с этого домена и какие.
В том случае если вы передаёте в get-запросе, например, заголовок «Authorization», то должны отдать «пачку» заголовков:
Access-Control-Allow-Credentials: true // Тут перечисляем наши заголовки Access-Control-Allow-Headers: Authorization, прочие заголовки запроса... // Перечисляем разрешённые методы Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS // Пишем домен с которого отправляем запрос Access-Control-Allow-Origin: https://yourdomain.ru
Когда fetch получит эти заголовки, то автоматически отправит уже GET-запрос. Вы можете легко проверить это, если посмотрите, например, в DevTools хрома (вкладка Network)
В том случае если заголовки не будут переданы, то в status code от сервера вы увидите «405 method not allowed». Также вы увидите это, если не перечислите нужный метод в «Access-Control-Allow-Methods»
Ссылка на оригинальные примеры https://fetch.spec.whatwg.org/#example-cors-with-credentials
Если вы не понимаете, что такое fetch, предлагаю прочитать мою статью «fetch в reactjs«, возможно также вам понадобиться статья про router в react
Надеюсь я сэкономил вам немного времени, ведь именно это основная цель моего сайта. Если вы хотите и дальше экономить своё время, то обязательно подписывайтесь на обновления сайта с помощью push-уведомлений.