If you are a web developer, I believe you have encountered a CORS error before during development when working with an API. There are around 13,600 questions about a CORS error asked on Stackoverflow 🥲:
If you haven’t, you might encounter the error at some point in your development journey. Usually, you will see an error message Access to XMLHttpRequest has been blocked by CORS policy on the browser console followed by a cause like one of these below:
No ‘Access-Control-Allow-Origin’ header presentNo ‘Access-Control-Allow-Headers’ headers presentMethod not supported under Access-Control-Allow-Methods header
Before finding solutions to fix the error, we need to understand what is CORS.
What is CORS
The name explains itself, Cross-Origin Resource Sharing (CORS)is an HTTP mechanism that allows resource sharing from one origin to another origin securely. It is a mechanism for relaxing the same-origin policy of modern internet browsers.
Two URLs would be considered to be having different origins when they have different protocols, ports (if specified), or hosts.
For example, making a request from https://domain-a.com to https://domain-b.com/api-1 is considered cross-origin as they have different hostnames.
Internet browsers follow the same-origin policy and restrict cross-origin HTTP requests initiated from scripts. This means that a website is only allowed to make requests to the same origin unless the response from other origins includes the right CORS headers (the CORS headers will be listed in the next section of this article).
The same-origin policy is a security measure to prevent Cross-Site Request Forgery (CSRF). Without this policy, a malicious website would be able to read your sensitive information on another website by making an HTTP request to the website.
The same-origin policy only restricts on-page scripts from accessing data or posting data to a different origin. Other resources such as images and CSS are not restricted and can be accessed from other origins.
To access data from other origins or post data to them, CORS is needed.
Why Does a CORS Error Occur
CORS supports requests and data transfers between cross-origin browsers and servers to be carried out securely. It relies on a mechanism that checks whether the server will permit requests from other origins to make sure that the cross-origin requests are safe.
Whenever a website tries to make a cross-origin request, the browser will add these CORS headers to the request:
OriginAccess-Control-Request-MethodAccess-Control-Request-Headers
The server will return a response with some of these CORS headers to allow or block the request:
Access-Control-Allow-OriginAccess-Control-Allow-CredentialsAccess-Control-Expose-HeadersAccess-Control-Max-AgeAccess-Control-Allow-MethodsAccess-Control-Allow-Headers
A CORS error occurs when the server doesn’t return the CORS headers required.
For example, https://domain-a.com tries to make an API request to https://domain-b.com that doesn’t allow it to access its resources. As https://domain-a.com is not included in the Access-Control-Allow-Origin header of the response, the browser will display a CORS error.
How to Fix a CORS Error
Solution 1: Configure the Backend to Allow CORS
If you have access to the backend service, you can configure the backend to handle CORS requests if they are allowed.
The basic requirement is to add Access-Control-Allow-Origin to the response header to specify the origin that is allowed to access resources from the server.
You can configure the backend to return this in the response header:
Access-Control-Allow-Origin: https://domain-a.com
This will allow https://domain-a.com to make a cross-origin request to your server. However, only an origin can be added.
If you want to allow multiple origins, you can do it dynamically by reading the Origin header from the request and set it as the value for Access-Control-Allow-Origin.
Another option would be to set the header to Access-Control-Allow-Origin: * to allow requests from any URL. However, you need to be careful when using this as it could cause your server to be vulnerable to CSRF attacks.
Different backend frameworks need to be configured differently to add the CORS headers. This W3C Wiki shows you how to add the headers to popular servers like Apache, nginx, Jetty, etc.
If you are using an external API service and cannot configure the backend to accept CORS requests, you can try one of the methods below.
Solution 2: Use a Proxy Server
As the same-origin policy is implemented by internet browsers and not enforced within server-to-server communication, you can use a proxy server to call the external API.
A proxy server acts as a middleware between the client and the server. Instead of making a request from the client to the external API directly, you can make a request to the proxy server. The proxy server will make a request to the external API for you and return the response that it receives from the external API.
As a CORS error occurs when the external API server doesn’t return the HTTP headers required by the CORS standard, you can add the missing header like Access-Control-Allow-Origin: * and return the response to the browser using a proxy server.
You can either create your own proxy server or use a CORS proxy server like CORS Anywhere to retrieve data from the external API. One thing to note is that the CORS Anywhere proxy server is shared, it might be a bit slow sometimes. If you need to call the external API frequently, creating your own proxy server might be a better option.
Solution 3: Bypass the Error Using a Browser Extension
This method is not a proper solution to fix the error as it only works on your local computer which has the extension installed. However, you can use this method when you need to make a cross-origin request during development only.
To get rid of a CORS error, you can download a browser extension like CORS Unblock. The extension appends Access-Control-Allow-Origin: * to every HTTP response when it is enabled. It can also add custom Access-Control-Allow-Origin and Access-Control-Allow-Methods headers to the responses.
The way it gets rid of the CORS error is the same as using a CORS proxy server as mentioned above but this method only works on a computer with the extension installed. Therefore, you should not treat it as a real solution to fix a CORS error and should use it for development only.
Another thing to take note of is that all web requests will be monitored and response headers mentioned above will be appended when the extension is enabled. Therefore, you should only enable the extension when you need to use it and keep it disabled at other times.
When Not to Fix a CORS Error
Not every API can be used by a client. Some APIs are designed for server-side use, like the Google Maps Places API. If you try to access the API from a client, you will get a CORS error:
Although you could get rid of the error using one of the solutions above, Google strongly recommends developers to use the Google Map Places client library.
Many third-party API services provide client libraries to reduce the difficulty for developers to implement third-party functionality in their apps. For example, Bannerbear has client libraries in Ruby, Node.js and PHP for developers to integrate its Image Generation API into their apps easily.
If you are using a third-party API on the front-end and they have a client library, using the client library might be an easier option as you can avoid situations where you might run into a CORS error.
Всем привет!
Меня зовут Радик, я frontend developer компании Creative. И сегодня я хочу поднять тему, которая касается и фронта и бэка, и окружает нас с вами каждый день. Речь пойдёт об ошибках CORS и как их можно обойти.
Уверен, что многим разрабам знакома ситуация, когда ты работаешь над приложением, оно запущено локально, и тебе нужно сделать из него запросы к различным удалённым ресурсам. В этот момент «что-то идёт не так», и ты видишь на своём экране миллион ошибок в консоли браузера. Почему они появляются? Давайте разбираться вместе. В этой статье расскажу о средствах защиты браузера и о том, что он может от вас скрывать в процессе кроссдоменных запросов. Фича: об ошибках будем говорить в картинках 
SOP – Same Origin Policy
Рассмотрим кейс. Мы заходим на какой-то незнакомый сайт, созданный потенциальным злоумышленником. Внешне пока не видим ничего подозрительного, но пока мы находимся на этом сайте, злобный скрипт уже готовит запрос к серверу банка, в котором мы залогинены, и пытается получить наши данные:
Как же браузер пытается нас от этого защитить? Он использует Политику одинакового источника: Same Origin Policy (SOP).
В тех случаях, когда запрос отправляется на ресурс, у которого отличается домен / порт / протокол, – браузер по умолчанию понимает, что он кроссдоменный и применяет политику безопасности:
CORS – Cross Origin Resource Sharing
Что же делать в том случае, когда нам необходимо разрешить для браузера взаимодействие между различными ресурсами?
Браузер должен отправить в запросе заголовок:
**origin: htttps://good-website.com**
Сервер проверит, откуда к нему пришёл запрос, и (если этот домен разрешён) в ответе вернёт заголовок:
**access-control-allow-origin: htttps://good-website.com**
ACAH – Access-Control-Allow-Headers
Браузер может запретить доступ к некоторым заголовкам ответа из кода, ничего не сообщив при этом разработчику.
Так получается, потому что по умолчанию при кроссдоменных запросах браузер разрешает чтение только следующих заголовков ответа:
-
Cache-Control
- Content-Language
- Content-Length
- Content-Type
- Expires
- Last-Modified
- Pragma
Поэтому в network вкладке браузера мы видим абсолютно все интересующие нас заголовки, а из кода JS они будут нам недоступны.
Для того чтобы браузер разрешил доступ к этим заголовкам, в ответе должен быть указан заголовок Access-Control-Allow-Headers.
В нём нужно перечислить заголовки, доступ к которым разрешён браузером:
Специальное значение * позволяет разрешить для использования любые заголовки, но только в том случае, если в изначальном запросе нет cookie или данных аутентификации. В противном случае оно будет рассматриваться как буквальное имя заголовка «*».
Proxy как одно из возможных решений проблемы при локальной разработке
Рассмотрим ещё один кейс. Нам нужно сделать кроссдоменный запрос из приложения, которое развёрнуто локально. Но такой запрос в нужный нам момент не обрабатывается должным образом на сервере. Картинка для понимания.
Как быть? Можно запустить локальный proxy сервер, который будет пересылать данные между нашим приложением и сервером, добавляя необходимые заголовки:
Можно сделать простой proxy сервер на Node.js для решения проблемы с кроссдоменными запросами:
- Для этого переходим в директорию, в которой вы хотите создать прокси сервер
- Инициализируем Node.js проект командой npm init
- Устанавливаем необходимые пакеты командой npm install cors express http-proxy-middleware
- Создаём файл index.js со следующим содержимым:
-
Запускаем proxy сервер командой node index.js
- Теперь мы можем использовать адрес и порт, указанный в proxy сервере в приложении во время разработки.
На этом всё. Мы рассмотрели причины возникновения ошибки CORS и одно из возможных решений при локальной разработке на фронте. Надеюсь, мой материал будет вам полезен. Буду рад продолжить обсуждение темы в комментариях. Всем хорошего дня и поменьше ошибок CORS!
В этой статье подробно разобрана история и эволюция политики одинакового источника и CORS, а также расписаны разные типы доступа между различными источниками, а также несколько оптимальных решений работы с ними.
Если вы давно хотели разобраться в CORS и вас достали постоянные ошибки, добро пожаловать под кат.
Ошибка в консоли вашего браузера
No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://example.com/
Access to fetch at ‘https://example.com’ from origin ‘http://localhost:3000’ has been blocked by CORS policy.
Я уверен, вам уже доводилось видеть похожие сообщения об ошибках в консоли вашего браузера. Если нет, не волнуйтесь, скоро увидите. Все программисты достаточно часто натыкаются на CORS-ошибки.
Эти всплывающие ошибки в процессе разработки просто раздражают. Но на самом деле, CORS — это невероятно полезный механизм в мире неправильно настроенных веб серверов, злоумышленников, орудующих в интернете и организаций, продвигающих веб-стандарты.
Но давайте-ка пойдем к истокам…
В начале был первый субресурс
Субресурс — это HTML элемент, который требуется вставить в документ или выполнить в контексте этого документа. В 1993 году был введен первый тег <img>. С появлением веб стал более красивым, но заодно и стал сложнее.
Верни мне мой 1993 г.
Как вы поняли, если ваш браузер отображает страницу с <img>, он должен запросить этот тег из источника. Если браузер запрашивает тег из источника, который отличается от получателя по схеме, в полностью определенному имени хоста или порту, то это и есть запрос между различными источниками (cross-origin request).
Источники & cross-origin
Источник идентифицируется следующей тройкой параметров: схема, полностью определенное имя хоста и порт. Например, <http://example.com> и <https://example.com> имеют разные источники: первый использует схему http, а второй https. Вдобавок, портом для http по умолчанию является 80, тогда как для https — 443. Следовательно, в данном примере 2 источника отличаются схемой и портом, тогда как хост один и тот же (example.com).
Таким образом, если хотя бы один из трех элементов у двух ресурсов отличается, то источник ресурсов также считается разным.
Если, к примеру, мы будем сравнивать источник <https://blog.example.com/posts/foo.html> с другими источниками, то мы получим следующие результаты:
Пример запроса между различными источниками: когда ресурс (то есть, страница) типа <http://example.com/posts/bar.html> попробует отобразить тег из источника <https://example.com> (заметьте, что схема поменялась!).
Слишком много опасностей запроса между различными источниками
Теперь, когда мы определились, что такое совместное использования ресурсов между разными и одинаковыми источниками, давайте посмотрим, в чем же дело.
Когда тег <img> появился во Всемирной Паутине, мы тем самым открыли ящик Пандоры. Некоторое время спустя в Сети появились теги <script>, <frame>, <video>, <audio>, <iframe>, <link>, <form> и так далее. Эти теги могут быть загружены браузером уже после загрузки страницы, поэтому они все могут быть запросами в пределах одного источника и между о разными источниками.
Давайте погрузимся в воображаемый мир, где не существует CORS и веб-браузеры допускают все типы запросов между источниками.
Предположим, у меня есть страница на сайте evil.com с <script>. На первый взгляд это обычная страница, где можно прочесть полезную информацию. Но я специально создал код в теге <script>, который будет отправлять специально созданный запрос по удалению аккаунта (DELETE/account) на сайт банка. Как только вы загрузили страницу, JavaScript запускается и AJAX-запрос попадает в API банка.
Вжух, нет вашего аккаунта
Немыслимо, да? Представьте, что вы читаете что-то на веб странице и вам приходит электронное письмо от банка, что ваш аккаунт успешно удален. Знаю, знаю… если бы было так просто провести любую банковскую операцию… Отвлекся.
Для того чтобы мой вредоносный <script> сработал, ваш браузер должен был также отправить ваши учетные данные (куки), взятые с банковского сайта, как часть запроса. Именно таким образом банковские серверы идентифицировали бы вас и знали, какой аккаунт нужно удалить.
Давайте рассмотрим другой, не такой зловещий сценарий.
Мне нужно опознать людей, которые работают на Awesome Corp, внутренний сайт этой компании находится по адресу intra.awesome-corp.com. На моем сайте, dangerous.com, у меня есть <img src="https://intra.awesome-corp.com/avatars/john-doe.png">.
У пользователей, у которых нет активного сеанса с intra.awesome-corp.com, аватарка не отобразится, так как это приведет к ошибке. Однако если вы совершили вход во внутреннюю сеть Awesome Corp., как только вы откроете мой dangerous.com сайт, я буду знать, что у вас там есть аккаунт.
Это означает, что я смогу прощупать определенную информацию о вас. Конечно, для меня будет сложнее устроить атаку, но знание о том, что у вас есть доступ к Awesome Corp., является потенциальным направлением для атаки.
Утечка информации к 3-им лицам
Эти два примера крайне упрощены, но именно такие угрозы обусловили необходимость политики одинакового источника и CORS… Существуют разнообразные опасности, связанные с запросами между разными источниками. Некоторые из них можно сгладить, другие нет: они укоренены в природе интернета. Однако огромное количество заблокированных атак — это заслуга CORS.
Но до зарождения CORS существовала политика одинакового источника.
Политика одинакового источника
Политика одинакового источника предотвращает cross-origin атаки, блокируя доступ для прочтения загружаемых ресурсов из другого источника. Такая политика все еще разрешает нескольким тегам вроде <img> загружать ресурсы из других источников.
Политика одинакового источника введена Netscape Navigator 2.02 в 1995 году, изначально для защищенного cross-origin доступа к Объектной модели документа (DOM).
Даже несмотря на то, что внедрение политики одинакового источника не требует придерживаться определенного порядка действий, все современные браузеры следуют этой политике в той или иной форме. Принципы политики описаны в запросе на спецификацию RFC6454 Инженерного совета интернета (Internet Engineering Task Force).
Выполнение политики одинакового источника определено этим сводом правил:
Политика одинакового источника решает много проблем, но она довольно ограничительная. В век одностраничных приложений и сайтов, нагруженных медиа-контентом, эта политика не дает ни воздуха разработчикам, ни легко играться настройками.
CORS же появился с целью смягчения политики одинакового источника и для тонкой настройки доступа между различными источниками.
Врываемся в CORS
Я уже разъяснил, что такое источник, как он определяется, какие ошибки бывают у запросов с различными источниками и политику общего происхождения, выполняемые браузером.
Давайте разберемся с совместным использованием ресурсов различными источниками (CORS). CORS — это механизм, который дает контролировать доступ к тегам на веб странице по сети. Механизм классифицируется на три разные категории доступа тегов:
- Запись из разных источников
- Вставка из разных источников
- Считывание из разных источников
До того, как я начну объяснять каждую из этих категорий, очень важно понять, что несмотря на то, что браузер по умолчанию может разрешить определенный тип запросов между различными источниками, это не означает, что данный запрос будет принят сервером.
Запись из разных источников — это ссылки, переадресации и отправка форм. С активным CORS в вашем браузере все эти операции разрешены. Существует также штука под названием предварительный запрос, которая настраивает запись из разных источников. Таким образом, если некоторые записи могут быть разрешены, это не означают, что они будут выполнены на практике. Мы вернемся к этому немного позже.
Вставки из разных источников — это теги, загружаемые через <script>, <link>, <img>, <video>, <audio>, <object>, <embed>, <iframe> и т.п. Все они разрешены по умолчанию. <iframe> выделяется на их фоне, так как он используется для загрузки другой страницы внутри фрейма. Его обрамление в зависимости от источника может регулироваться посредством использования заголовка X-Frame-options.
Что касается <img> и других вставных тегов, то они устроены так, что сами инициируют запросы из разных источников cross-origin запроса. Именно поэтому в CORS существует различие между вставкой из разных источников и считыванием из разных источников.
Считывание из разных источников — это теги, загружаемые через вызовы AJAX/ fetch. Все они по умолчанию заблокированы вашим браузером. Существует обходной путь для вставки таких тегов на странице, но такие трюки регулируются другой политикой, которая соблюдается в современных браузерах.
Если ваш браузер обновлён, то он уже дополнен всей этой эвристикой.
Запись из разных источников
Операции записи из разных источников порой очень проблематичны. Давайте рассмотрим пример и посмотрим на CORS в деле.
Во-первых, у нас будет простой Crystal (с использованием Kemal) HTTP сервер:
require "kemal"
port = ENV["PORT"].to_i || 4000
get "/" do
"Hello world!"
end
get "/greet" do
"Hey!"
end
post "/greet" do |env|
name = env.params.json["name"].as(String)
"Hello, #{name}!"
end
Kemal.config.port = port
Kemal.run
Он просто берет запрос по ссылке /greet с name в теле запроса и возвращает Hello #{name}!. Чтобы запустить это маленький Crystal сервер мы можем написать
$ crystal run server.cr
Так запускается сервер, который будет слушать localhost:4000. Если мы откроем localhost:4000 в нашем браузере, то появится страница с тривиальным «Hello World».
Hello world!
Теперь, когда мы знаем, что наш сервер работает, давайте из консоли нашего браузера сделаем запрос POST /greet на сервер, слушающий localhost:4000,. Мы можем это сделать, используя fetch:
fetch(
'http://localhost:4000/greet',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Ilija'})
}
).then(resp => resp.text()).then(console.log)
Как только мы его запустили, мы увидим, что приветствие вернется из сервера:
Привет!
Это был POST запрос, но не из разных источников. Мы отправили запрос из браузера, где была отображена страница с адреса http://localhost:4000 (источник), к тому же источнику.
Теперь, давайте попробуем повторить такой же запрос но с различными источниками. Мы откроем https://google.com и попробуем тот же запрос с той же вкладки нашего браузера:
Привет, CORS!
Мы смогли добраться до знаменитой ошибки CORS. Несмотря на то что наш Crystal сервер смог совершить запрос, наш браузер защищает нас от нас самих. Он говорит нам, что сайт, который мы открыли, хочет внести изменения на другом сайте.
В первом примере, где мы отправили запрос в http://localhost:4000/greet из вкладки, которая отображала http://localhost:4000, наш браузер смотрит на запрос и разрешает, так как ему кажется, что наш сайт запрашивает наш сервер (что есть отлично). Но во втором примере где наш сайт (https://google.com) хочет написать на http://localhost:4000, тогда наш браузер отмечает этот запрос и не разрешает ему пройти.
Предварительные запросы
Если поподробнее разобраться в консоли разработчика, в частности, заглянуть во вкладку Network, то на самом деле мы увидим два запроса вместо одного, что мы отправили:
Как видно в панеле Network, отправленных запроса две штуки
Интересно заметить, то у первого запроса в HTTP фигурирует метод OPTIONS, в то время как у второго – метод POST.
Если внимательно посмотреть на запрос OPTIONS, то мы увидим, что этот запрос отправлен нашим браузером до отправления запроса POST.
Смотрим запрос OPTIONS
Интересно, что несмотря на то, что статус запроса OPTIONS был HTTP 200, он был все же отмечен красным в списке запросов. Почему?
Это предварительный запрос, который делают современные браузеры. Предварительные запросы выполняются перед запросами, которые CORS считает сложными. Признаки, свидетельствующие о сложности запроса:
- Запрос использует методы отличные от GET, POST, или HEAD
- Запрос включает заголовки отличные от Accept, Accept-Language или Content-Language
- Запрос имеет значение заголовка Content-Type отличное от application/x-www-form-urlencoded, multipart/form-data, или text/plain.
Следовательно, в примере выше, несмотря на то, что мы отправили запрос POST, браузер считает наш запрос сложным из-за заголовка Content-Type: application/json.
Если бы мы изменили наш сервер так, чтобы он обрабатывал контент text/plain (вместо JSON), мы бы могли обойтись без предварительных запросов:
require "kemal"
get "/" do
"Hello world!"
end
get "/greet" do
"Hey!"
end
post "/greet" do |env|
body = env.request.body
name = "there"
name = body.gets.as(String) if !body.nil?
"Hello, #{name}!"
end
Kemal.config.port = 4000
Kemal.run
Теперь, когда мы можем отправить наш запрос с заголовком Content-type: text/plain:
fetch(
'http://localhost:4000/greet',
{
method: 'POST',
headers: {
'Content-Type': 'text/plain'
},
body: 'Ilija'
}
)
.then(resp => resp.text())
.then(console.log)
Теперь, пока предварительный запрос не будет отправлен, CORS политика браузера будет постоянно блокировать запрос:
CORS стоит насмерть
Но так как мы создали запрос, который не классифицируется как сложный, наш браузер не заблокирует запрос.
Запрос прошел
Проще говоря, наш сервер неправильно настроен на принятие text/plain запросов из разных источников без какой-либо защиты и наш браузер не сможет ничего с этим поделать. Но все же он делает следующую вещь: он не показывает нашу открытую страницу/вкладку в ответ на это запрос. Следовательно, в этом случае CORS не блокирует запрос, он блокирует ответ.
CORS политика вашего браузера считает, что это фактически считывание из разных источников, так как, несмотря на то, что запрос был отправлен как POST, Content-type значение заголовка по сути приравнивает его к GET. Считывания из разных источников заблокированы по умолчанию, следовательно мы видим заблокированный запрос в нашей панели Network.
Не рекомендуется избегать предварительных запросов, то есть, действовать, как в вышеприведенном примере. Если вы ожидаете, что ваш сервер должен будет незаметно обрабатывать предварительные запросы, то на в таком случае он должен будет реализовывать конечные точки для приема запросов OPTIONS и возвращать правильные заголовки.
Выполняя запрос OPTIONS, вы должны помнить, что предварительный запрос браузера проверяет наличие трех заголовков, которые могут быть в ответе:
Access-Control-Allow-Methods, который указывает на то, какие методы поддерживаются URL-ом ответа в контексте CORS протокола.Access-Control-Allow-Headers, который указывает, на то, какие заголовки поддерживаются URL-ом ответа в контексте CORS протокола.Access-Control-Max-Age, который указывает число секунд (5 по умолчанию) и это значение соответствует периоду, на который предоставляемая заголовкамиAccess-Control-Allow-MethodsиAccess-Control-Allow-Headersинформация может быть кэширована.
Давайте вернемся к предыдущему примеру, где мы отправили сложный запрос:
etch(
'http://localhost:4000/greet',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Ilija'})
}
).then(resp => resp.text()).then(console.log
Мы уже выяснили, что когда мы отправляем запрос, наш браузер будет сверяться с сервером, можно ли выполнить запрос с данными из разных источников. Чтобы обеспечить работоспособность в среде с разными источниками, мы должны сначала добавить конечную точку OPTIONS/greet к нашему серверу. В заголовке ответа новая конечная точка должна сообщить браузеру, что запрос на POST /greet с заголовком Content-type: application/json из источника https://www.google.com может быть принят.
Мы это сделаем, используя заголовки Access-Control-Allow-*:
options "/greet" do |env|
# Allow `POST /greet`...
env.response.headers["Access-Control-Allow-Methods"] = "POST"
# ...with `Content-type` header in the request...
env.response.headers["Access-Control-Allow-Headers"] = "Content-type"
# ...from https://www.google.com origin.
env.response.headers["Access-Control-Allow-Origin"] = "https://www.google.com"
end
Если мы запустим сервер и отправим запрос, то
Все еще заблокирован?
Наш запрос остается заблокированным. Даже несмотря на то что наша конечная точка OPTIONS/greet в самом деле разрешила запрос, мы пока еще видим сообщение об ошибке. В нашей панели Network происходит кое-что интересное:
OPTIONS стал зеленым!
Запрос в конечную точку OPTIONS/greet прошел успешно! Однако запрос POST /greet все еще терпит неудачу. Если взглянуть на внутрь запроса POST /greet мы увидим знакомую картинку:
POST тоже стал зеленым?
На самом деле запрос удался: сервер вернул HTTP 200. Предварительный запрос заработал. Браузер совершил POST-запрос вместо того, чтобы его заблокировать. Однако ответ на запрос POST не содержит никаких CORS заголовков, так что даже несмотря на то, что браузер сделал запрос, он заблокировал любой ответ.
Чтобы разрешить браузеру обработать ответ из запроса POST /greet, нам также нужно добавить заголовок CORS к конечной точке POST:
post "/greet" do |env|
name = env.params.json["name"].as(String)
env.response.headers["Access-Control-Allow-Origin"] = "https://www.google.com"
"Hello, #{name}!"
end
Добавляя к заголовку Access-Control-Allow-Origin заголовок ответа, мы сообщаем браузеру, что вкладка с открытой https://www.google.com имеет доступ к содержимому ответа.
Если попытаться еще разок, то
POST работает!
Мы увидим, что POST /greet получил для нас ответ без каких-либо ошибок. Если посмотреть на панели Network, то мы увидим, что оба запроса зеленые.
OPTIONS & POST в деле!
Используя надлежащие заголовки ответа в нашем конечной точке OPTIONS /greet, выполнявшей предварительный запрос, мы разблокировали конечную точку POST /greet нашего сервера, так, чтобы он имел доступ к информации из разных источников. Вдобавок, предоставляя правильный CORS заголовок ответа в ответе конечной POST /greet, мы позволили браузеру обрабатывать ответы без возникновения блокировок.
Считывание из разных источников
Как мы отмечали ранее, считывание из разных источников блокируются по умолчанию. Это делается намеренно: мы не хотели бы загружать другие ресурсы из других источников в пределах нашего источника.
Скажем, в нашем Crystal сервере есть действие GET /greet.
get "/greet" do
"Hey!"
end
Из нашей вкладки, что передала www.google.com если мы попробуем запросить эндпоинт GET /greet, то CORS нас заблокирует:
CORS блокирует
Если посмотрим поглубже в запрос, то мы найдем кое-что интересное:
На самом деле, как и прежде, наш браузер разрешил запрос: мы получили код состояния HTTP 200. Однако он не показал нашу открытую страницу/вкладку в ответ на этот запрос. Еще раз, в данном случае CORS не заблокировал запрос, он заблокировал ответ.
Так же, как и в случае с записью из разных источников, мы можем освободить CORS и обеспечить считывание из разных источников, добавляя заголовок Access-Control-Allow-Origin:
get "/greet" do |env|
env.response.headers["Access-Control-Allow-Origin"] = "https://www.google.com"
"Hey!"
end
Когда браузер получит ответ от сервера, он проверит заголовок Access-Control-Allow-Origin и исходя из его значения решит, сможет ли он позволить странице прочитать ответ. Учитывая, что в данном случае значением является https://www.google.com, итог будет успешным:
Успешный запрос GET между разными источниками
Вот как наш браузер защищает нас от считывания из разных источников и соблюдает директивы server, сообщенные через заголовки.
Тонкая настройка CORS
Как мы уже видели в предыдущих примерах, чтобы смягчить политику CORS нашего сайта мы можем присвоить опцию Access-Control-Allow-Origin для нашего действия /greet значению https://www.google.com:
post "/greet" do |env|
body = env.request.body
name = "there"
name = body.gets.as(String) if !body.nil?
env.response.headers["Access-Control-Allow-Origin"] = "https://www.google.com"
"Hello, #{name}!"
end
Это разрешит нашему источнику https://www.google.com запросить наш сервер, и наш браузер свободно сделает это. Имея Access-Control-Allow-Origin мы можем попробовать снова выполнить вызов fetch:
Сработало!
И это работает! С новой политикой CORS мы можем вызвать действие /greet из нашей вкладки, в которой загружена страница https://www.google.com. Или, мы могли бы присвоить заголовку значение *, которое сообщило бы браузеру, что сервер может быть вызван из любого источника.
Устанавливая такую конфигурацию, нужно тщательно взвесить все риски. Тем не менее, вставка заголовков с нестрогими требованиями к CORS почти всегда безопасна. Есть эмпирическое правило: если вы открываете URL в приватной вкладке и вас устраивает информация, которая там отображается, то вы можете установить разрешающую CORS политику (*) для данного URL.
Другой способ настройки CORS на нашем сайте — это использование заголовка запроса Access-Control-Allow-Credentials. Access-Control-Allow-Credentials запрашивает браузер, показывать ли ответ JavaScript коду клиентской части, когда в качестве режима учетных данных запроса используется include.
Учетный режим запросов исходит из внедрения Fetch API, который в свою очередь корнями идет к объектам XMLHttpRequest:
var client = new XMLHttpRequest()
client.open("GET", "./")
client.withCredentials = true
С вводом fetch, метод withCredentials превратился в опциональный аргумент fetch запроса:
fetch("./", { credentials: "include" }).then(/* ... */)
Доступными опциями для обработки учетных данных являются omit, same-origin и include. Доступны разные режимы, так что программист может настроить отправляемый запрос, пока ответ от сервера сообщает браузеру как вести себя, когда учетные данные отправлены с запросом (через заголовок Access-Control-Allow-Credential).
Спецификация Fetch API содержит подробно расписанный и детально разобранный функционал взаимодействия CORS и Web API fetch, а также характеризует механизмы безопасности, используемые браузерами.
Несколько правильных решений
В завершении, давайте рассмотрим некоторые из рекомендуемых методов, касающихся совместного использования ресурсов между разными источниками (CORS).
Свободный доступ для всех
Как правило, это тот случай, когда у вас есть сайт с открытым контентом, не ограниченный платным доступом или сайт, требующий аутентификацию или авторизацию. Тогда вы должны установить Access-Control-Allow-Origin: * для ресурсов сайта.
Значение * хорошо подойдет в случаях, когда
- Не требуется ни аутентификация, ни авторизация
- Ресурс доступен широкой аудитории пользователей без ограничений
- Источников и клиентов, у которых будет доступ к ресурсам великое множество, и вы не знаете о них или вам все равно, кто они.
Опасные последствия от применения такой конфигурации наступают, когда контент подается на частном сервере (то есть за брандмауэрами или VPN). Когда вы подключены через VPN, у вас есть доступ к файлам в сети компании:
Сверхупрощение VPN
Теперь, когда взломщик захостит dangerous.com, который содержит ссылку файла с VPN, то (в теории) он может создать скрипт в их сайте, который сможет иметь доступ к этому файлу:
Утечка файла
В то время как атаку такого типа сложно устроить и это требует широких знаний о конкретном VPN и файлах, хранящихся в нем, это потенциальный вектор атаки, о которым мы должны знать.
Всё в семью
Продолжая этот пример, представим, что вы хотите провести аналитику нашего сайта. Мы хотели бы, чтобы браузеры наших пользователей отправляли нам данные о том, как пользователи взаимодействуют с сайтом и о поведении наших пользователей на нашем сайте.
Проще всего это сделать, периодически отправляя данные, реализовав в браузере асинхронные запросы с помощью JavaScript. На машинном интерфейсе у нас есть простой API, который берет эти запросы из браузеров наших пользователей и хранит данные на машине для последующей обработки.
В таких случаях наш API общедоступен, но мы не хотим, чтобы какой-либо сайт прислал данные в наш аналитический API. На самом деле мы заинтересованы только в запросах, исходящих из браузеров, открывавших наш сайт, вот и все.
В данных случаях мы хотим, чтобы наш API установил заголовок Access-Control-Allow-Origin к URL нашего сайта. Это обеспечит нас тем, что браузеры никогда не отправят запросы нашему API с других страниц.
Если пользователи или другие сайты попробуют взломать данные нашего аналитического API, то набор заголовков Access-Control-Allow-Origin, установленный на нашем API, не пропустит запрос.
Null источник
Другим интересным случаем является null источник. Это происходит, когда ресурс получает доступ от браузера, который отображает локальный файл. Например, запросы, исходящие из определенного JavaScript, работающего в статическом файле на вашем ПК, имеют заголовок Origin со значением null.
В таких случаях, если ваш сервер разрешает доступ к ресурсам для null источников, то это может мешать продуктивности разработчика. Разрешение null источников в политике CORS должно быть сделано намеренно, и только если пользователями вашего сайта/продукта пока являются только его разработчики.
Пропускай куки, если возможно
Как мы уже видели с Access-Control-Allow-Credentials, куки не включены по умолчанию. Чтобы разрешить отправку куки с разных источников, нужно просто вернуть Access-Control-Allow-Credentials: true. Этот заголовок сообщит браузерам, что им разрешается пересылать удостоверяющие данные (то есть куки) в запросах между разными источниками.
Разрешение куки между разными источниками – часто ненадежная практика. Вы можете подставиться под потенциальные атаки, так что включайте куки их только когда это абсолютно необходимо.
Куки между разными источниками полезнее всего в ситуациях, когда вы точно знаете какие именно клиенты будут иметь доступ к вашему серверу. Именно поэтому семантика CORS не позволяет нам установить Access-Control-Allow-Origin: *, когда удостоверяющие данные между разными источниками разрешены.
В то время как комбинация из Access-Control-Allow-Origin: * и Access-Control-Allow-Credentials: true технически разрешается, она является анти-паттерном и ее следует безусловно избегать.
Если вы хотите, чтобы к вашим серверам имели доступ разные клиенты и источники, то вам стоит рассмотреть возможность создания API с аутентификацией через пароль вместо использования куков. Но если вариант с API не является оптимальным, то обеспечьте себя защитой от фальсификатора межсайтовых запросов (CSRF).
Дополнительная литература
Надеюсь, что этот длинный текст помог вам подробно разобраться в CORS, как появилась эта штука и почему она необходима. Вот ссылки, которые я использовал при написания своей статьи.
- Cross-Origin Resource Sharing (CORS)
- Access-Control-Allow-Credentials header on MDN Web Docs
- Authoritative guide to CORS (Cross-Origin Resource Sharing) for REST APIs
- The «CORS protocol» section of the Fetch API spec
- Same-origin policy on MDN Web Docs
- Quentin’s great summary of CORS on StackOverflow
Наши серверы можно использовать для разработки и хостинга сайтов любой сложности.
Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!
Cross-Origin Resource Sharing (CORS) is a mechanism or a protocol that allows devices on one domain to access resources residing on other domains.
Generally, for security reasons, browsers forbid requests that come in from cross-domain sources. However, there could be cases where you want to overcome this and access cross-domain resources, and CORS makes this possible.
There is another concept known as Same-Origin Policy (SOP) which enables resource sharing on similar domains. In this guide, we will discuss the SOP and how it helps to secure websites. We will also discuss how CORS extends the flexibility and adds pace to the SOP.
Same-Origin Policy (SOP)
The Same-Origin Policy was developed as a security mechanism for browsers to protect resources from malicious attacks. SOP allows resource sharing (data exchange) between two objects with the same origins.
This means that the origin of two websites should be the same if they want to share resources. For two websites to have the same origins, the websites should have the same domain, port number, and protocol type. If any one of these three properties is found different, then the sources are considered different origins.
While SOP is considered a restrictive system, it is highly secured, eliminating potential attacks on websites through cross-domain resources. SOP offers security to websites but can also be a hurdle to interact with third-party websites. Many dynamic websites regularly share a cross-domain resource with trusted websites and subdomains.
With SOP in place, access to cross-origin websites is restricted, and controlled access to resources is possible using Cross-Origin Resource Sharing (CORS).
Cross-Origin Resource Sharing (CORS)
CORS was introduced to provide easy and quick access to subdomains and trusted third parties. CORS enables controlled resource sharing between cross-domain origins as discussed earlier.
The browser and cross-origin website you are trying to access collectively exchange HTTP headers that define trusted web origins and associated properties for access control, as defined by CORS specifications.
CORS offers controlled access to cross-domain websites and hence is well secured. CORS is widely implemented to tackle limitations introduced by SOP. However, if poorly executed, CORS can cause severe security risks. Improper configuration of CORS may present some challenges and errors.
Let us learn more about CORS errors and best practices to avoid them.
Understanding and Fixing CORS Error
CORS is implemented on the server-side; it cannot be reconfigured on the client-side. The CORS behavior, commonly termed as CORS error, is a mechanism to restrict users from accessing shared resources.
This is not an error but a security measure to secure users or the website which you are accessing from a potential security breach.
This breach may occur due to incomplete or improper HTTP headers on the client-side implementation (eg. missing authorization data such as API key).
Best Practices to Avoid CORS Errors
Let’s discuss some of the best practices for avoiding CORS errors by using a Custom Field Extension in Contentstack.
Proxy the API Request While Using External Calls in an Extension
CORS proxy can be a helpful solution to make cross-origin requests. The proxy layer sits between your request and its destination without knowing the request’s origin.
Thus, though the request comes from an unknown source, the CORS proxy makes it seem that it is a request from an allowed location.
To learn how to do this, here is the list of CORS-Proxy which you can use for your website.
Free proxies can be great for testing, but relying on a free third-party tool is not advisable for something you will use on the production site. In such cases, a more stable solution is to call the API from a server and make the data available on the client-side.
Use a Serverless Function
A more commonly used solution to resolve CORS error is to use a serverless function. It is an alternate way to proxy your requests, but instead of relying on a free third-party service, you can build your micro-infrastructure to call a web service and feed data to an API endpoint.
Popular serverless functions include AWS Lambda, Azure Functions, and Google Cloud functions. These services allow you to get just enough server space to run a function or two, as you do not need much space to run a function that calls a web service to return some data.
To understand this better, let’s consider a use case:
A user wants to create an extension in Contentstack which will fetch the maps data from the Google Maps API to get data related to longitude and latitude within your entry.
The user has to follow the steps below:
- Create a custom extension in Contentstack using the UI extension SDK. This extension will make an API call to the Google Maps API to fetch the data in your entry.
The request will fail due to the cross-origin policy set at Google’s server, which will not let you fetch the data, resulting in a CORS error at your client-side. - To solve this problem, you can create an API Gateway URL in AWS with CORS enabled to serve the responses from the Google Maps API with our AWS Lambda function. For example:
exports.handler = async (event) => { try{ return { statusCode: 200, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token', 'Access-Control-Allow-Methods': 'OPTIONS,POST', 'Access-Control-Allow-Credentials': true, 'Access-Control-Allow-Origin': '*', 'X-Requested-With': '*', }, body: JSON stringified object, }; } catch (error) { console.log(error); return { statusCode: 500, headers: { 'Content-Type': 'application/json', 'Access-Control-Allow-Headers': 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token', 'Access-Control-Allow-Methods': 'OPTIONS,POST', 'Access-Control-Allow-Credentials': true, 'Access-Control-Allow-Origin': '*', 'X-Requested-With': '*', }, body: JSON.stringify({ name: error.name, code: error.code, message: error.message, }), }; } };
- The extension that we have created will make an appropriate call i.e., GET, POST, and so on, to our API Gateway URL & trigger our lambda function.
- The AWS Lambda will then return the maps API response to our client-side. This will fetch the maps data from Google Maps within your entry in Contentstack.
Note: For external hosting of the extension source code, the ‘allow-same-origin’ option will be enabled. Here, the origin refers to the domain where you’ve hosted the extensions. Also, ensure that the response header has the exact origin URL passed in the request header.
For Contentstack-hosted extensions, the iframe’s origin will be null as we don’t enable the ‘allow-same-origin’ flag.
CORS Issue in Frontend Frameworks
The CORS configuration can sometimes be tricky to achieve, and hence developers serve both the backend and frontend under the same domain in production.
To minimize this effort and provide flexibility to work with CORS, React, Nuxt, Express, etc. allow users to replicate the setup in development. This setup is done at the server end, so whenever a cross-origin request is made to the server, we can use a middleware like http-proxy-middleware to proxy requests.
Furthermore, we can also redirect and change the path of the request by using options like pathRewrite.
Do you understand CORS and why you get CORS errors? Let’s understand the basics behind this security practice and one of the most common errors you’ll see.
When we first start making our apps, we often make one mistake that can send us in the wrong direction. A “CORS error” message shows up when we try to get a resource from a different domain. We don’t know what to do or how to make this
work. There are a lot of developers who know about the error but don’t know how it works or how to fix it.
Let’s get started and learn about one of the most common mistakes and why it’s such a big deal.
What is CORS?
CORS helps to ensure that only authorized domains can access sensitive data or resources, and that web applications are not vulnerable to cross-site scripting (XSS) attacks or other types of security vulnerabilities by allowing web developers to explicitly
allow or block cross-origin requests. It also allows webpages to make explicit cross-origin requests for resources while preventing unauthorized access to other resources.
Before CORS, webpages could only make requests to the same domain that served the web page, a practice known as the “same-origin policy.” This policy was implemented to prevent malicious websites from sending unauthorized requests to other
domains, which could expose sensitive data or compromise security.
However, as the web evolved and web applications became more complex, so did the requirement for webpages to be able to make requests to external domains. Web applications, for example, may need to access APIs or other data sources hosted on different
domains, or they may need to include external domain resources such as images or stylesheets.
CORS was created to address this need while still maintaining web security. It allows web developers to specify which domains are allowed to access their resources, as well as control the types of requests that can be made and the types of data that can
be returned.
What are CORS Errors?
CORS errors happen when a webpage makes a request to a different domain than the one that served the page, and the server responds with an HTTP error because the “Origin” header in the request is not allowed by the server’s CORS configuration.
The Reason for CORS
We discussed what a CORS error is and how it works, but to fully understand it, let’s use a simple analogy.
Consider that both you and your friend have websites. Your website represents one domain on the internet, while your friend’s website represents another. To keep your websites distinct and secure, there are regulations in place that restrict your
website’s access to resources (such as photos or data) outside of your domain. This is similar to the “same-origin policy” in web browsers, which only permits webpages to access resources from the same domain from which the page
was served.
Imagine that you wish to include an image from a friend’s website on your own. You wish you could use the image, but you cannot since it is private property and you lack permission to do so. This is analogous to a webpage attempting to access a
resource from a domain other than the one that delivered the webpage but being unable to do so due to the same-origin policy.
To solve this issue, your friend could permit you to use the image under certain conditions. For instance, they may enable you to use the image if you first ask for approval and use it for a specific reason.
This is similar to CORS, which enables web developers to declare which domains are permitted to access their resources, as well as to regulate the types of requests that can be performed and the types of data returned. By permitting you to use the image
as long as you adhere to the regulations, your friend can continue to enjoy the privacy of their property (the image) while enabling you to use it on your website.
How to Solve CORS Errors
There are several ways to solve CORS errors, depending on the cause of the error and the specific requirements of your application. Here are some common approaches:
Allow Cross-Origin Requests on Server
If the server is blocking the request because the server’s CORS configuration does not allow the “Origin” header, you can configure the server to allow the request by adding the domain of the web page that requested the list of allowed
origins, or by setting the Access-Control-Allow-Origin header to “*” to allow any domain to access the resource.
The Access-Control-Allow-Origin header is an HTTP response header that is used in the case of CORS (Cross-Origin Resource Sharing). It specifies which domain is permitted to access the resource in the response.
When a webpage requests a different domain, the browser sends an HTTP request with an “Origin” header that indicates the domain of the webpage that made the request. Based on the value of the “Origin” header, the server can either
allow or deny the request. If the server accepts the request, it sends an HTTP response with an Access-Control-Allow-Origin header indicating the domain that is permitted to access the resource.
For our previous example, our page “https://my-website.com” requests our friend’s page “https://my-friend.com”, the server at “https://my-friend.com” could send the following response:
Access-Control-Allow-Origin: https://my-website.com
This allows our “https://my-website.com” page to access the resource but prevents any other domain from doing so.
The Access-Control-Allow-Origin header can also be set to “*” to allow access to the resource from any domain. However, use with caution because it may expose sensitive data or resources to unauthorized domains.
Allow Some HTTP Methods
When updating your CORS configuration, you must use the “Access-Control-Allow-Methods” header in your server’s HTTP responses to allow the necessary HTTP methods. This header indicates which HTTP methods are permitted in cross-origin
requests.
To allow the GET and POST methods, for example, include the following header in your server’s HTTP responses:
Access-Control-Allow-Methods: GET, POST
You can also use the “*” value to permit all HTTP methods, but this should be done with caution because it could expose your server to security vulnerabilities.
You may also need to update your server’s CORS configuration to allow the necessary HTTP headers in addition to specifying the allowed methods. This is accomplished through the use of the “Access-Control-Allow-Headers” header, which
specifies which HTTP headers are permitted in cross-origin requests.
Access-Control-Allow-Headers: Content-Type, Authorization
Conclusion
Overall, CORS is an important security feature that helps to ensure the safety and security of the web, and is essential for modern web applications that make cross-origin requests.
































