Cross-Origin Resource Sharing (CORS) is a standard that allows a server to relax the same-origin policy. This is used to explicitly allow some cross-origin requests while rejecting others. For example, if a site offers an embeddable service, it may be necessary to relax certain restrictions. Setting up such a CORS configuration isn’t necessarily easy and may present some challenges. In these pages, we’ll look into some common CORS error messages and how to resolve them.
If the CORS configuration isn’t setup correctly, the browser console will present an error like "Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at $somesite"
indicating that the request was blocked due to violating the CORS security rules. This might not necessarily be a set-up mistake, though. It’s possible that the request is in fact intentionally being disallowed by the user’s web application and remote external service. However, If the endpoint is meant to be available, some debugging is needed to succeed.
Identifying the issue
To understand the underlying issue with the CORS configuration, you need to find out which request is at fault and why. These steps may help you do so:
- Navigate to the web site or web app in question and open the Developer Tools.
- Now try to reproduce the failing transaction and check the console if you are seeing a CORS violation error message. It will probably look like this:
The text of the error message will be something similar to the following:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://some-url-here. (Reason: additional information here).
Note: For security reasons, specifics about what went wrong with a CORS request are not available to JavaScript code. All the code knows is that an error occurred. The only way to determine what specifically went wrong is to look at the browser’s console for details.
CORS error messages
See also
Many websites have JavaScript functions that make network requests to a server, such as a REST API. The web pages and APIs are often in different domains. This introduces security issues in that any website can request data from an API. Cross-Origin Resource Sharing (CORS) provides a solution to these issues. It became a W3C recommendation in 2014. It makes it the responsibility of the web browser to prevent unauthorized access to APIs. All modern web browsers enforce CORS. They prevent JavaScript from obtaining data from a server in a domain different than the domain the website was loaded from, unless the REST API server gives permission.
From a developer’s perspective, CORS is often a cause of much grief when it blocks network requests. CORS provides a number of different mechanisms for limiting JavaScript access to APIs. It is often not obvious which mechanism is blocking the request. We are going to build a simple web application that makes REST calls to a server in a different domain. We will deliberately make requests that the browser will block because of CORS policies and then show how to fix the issues. Let’s get started!
NOTE: The code for this project can be found on GitHub.
Table of Contents
- Prerequisites to Building a Go Application
- How to Build a Simple Web Front End
- How to Build a Simple REST API in Go
- How to Solve a Simple CORS Issue
- Allowing Access from Any Origin Domain
- CORS in Flight
- What Else Does CORS Block?
- Restrictions on Response Headers
- Credentials Are a Special Case
- Control CORS Cache Configuration
- How to Prevent CORS Issues with Okta
- How CORS Prevents Security Issues
Prerequisites to Building a Go Application
First things first, if you don’t already have Go installed on your computer you will need to download and install the Go Programming Language.
Now, create a directory where all of our future code will live.
Finally, we will make our directory a Go module and install the Gin package (a Go web framework) to implement a web server.
go mod init cors
go get github.com/gin-gonic/gin
go get github.com/gin-contrib/static
A file called go.mod
will get created containing the dependencies.
How to Build a Simple Web Front End
We are going to build a simple HTML and JavaScript front end and serve it from a web server written using Gin.
First of all, create a directory called frontend
and create a file called frontend/index.html
with the following content:
<html>
<head>
<meta charset="UTF-8" />
<title>Fixing Common Issues with CORS</title>
</head>
<body>
<h1>Fixing Common Issues with CORS</h1>
<div>
<textarea id="messages" name="messages" rows="10" cols="50">Messages</textarea><br/>
<form id="form1">
<input type="button" value="Get v1" onclick="onGet('v1')"/>
<input type="button" value="Get v2" onclick="onGet('v2')"/>
</form>
</div>
</body>
</html>
The web page has a text area to display messages and a simple form with two buttons. When a button is clicked it calls the JavaScript function onGet()
passing it a version number. The idea being that v1
requests will always fail due to CORS issues, and v2
will fix the issue.
Next, create a file called frontend/control.js
containing the following JavaScript:
function onGet(version) {
const url = "http://localhost:8000/api/" + version + "/messages";
var headers = {}
fetch(url, {
method : "GET",
mode: 'cors',
headers: headers
})
.then((response) => {
if (!response.ok) {
throw new Error(response.error)
}
return response.json();
})
.then(data => {
document.getElementById('messages').value = data.messages;
})
.catch(function(error) {
document.getElementById('messages').value = error;
});
}
The onGet()
function inserts the version number into the URL and then makes a fetch request to the API server. A successful request will return a list of messages. The messages are displayed in the text area.
Finally, create a file called frontend/.go
containing the following Go code:
package main
import (
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.Use(static.Serve("/", static.LocalFile("./frontend", false)))
r.Run(":8080")
}
This code simply serves the contents of the frontend
directory on requests on port 8080. Note that JavaScript makes a call to port http://localhost:8000
which is a separate service.
Start the server and point a web browser at http://localhost:8080
to see the static content.
How to Build a Simple REST API in Go
Create a directory called rest
to contain the code for a basic REST API.
NOTE: A separate directory is required as Go doesn’t allow two program entry points in the same directory.
Create a file called rest/server.go
containing the following Go code:
package main
import (
"fmt"
"strconv"
"net/http"
"github.com/gin-gonic/gin"
)
var messages []string
func GetMessages(c *gin.Context) {
version := c.Param("version")
fmt.Println("Version", version)
c.JSON(http.StatusOK, gin.H{"messages": messages})
}
func main() {
messages = append(messages, "Hello CORS!")
r := gin.Default()
r.GET("/api/:version/messages", GetMessages)
r.Run(":8000")
}
A list called messages
is created to hold message objects.
The function GetMessages()
is called whenever a GET request is made to the specified URL. It returns a JSON string containing the messages. The URL contains a path parameter which will be v1
or v2
. The server listens on port 8000.
The server can be run using:
How to Solve a Simple CORS Issue
We now have two servers—the front-end one on http://localhost:8080
, and the REST API server on http://localhost:8000
. Even though the two servers have the same hostname, the fact that they are listening on different port numbers puts them in different domains from the CORS perspective. The domain of the web content is referred to as the origin. If the JavaScript fetch
request specifies cors
a request header will be added identifying the origin.
Origin: http://localhost:8080
Make sure both the frontend and REST servers are running.
Next, point a web browser at http://localhost:8080
to display the web page. We are going to get JavaScript errors, so open your browser’s developer console so that we can see what is going on. In Chrome it is *View** > Developer > Developer Tools.
Next, click on the Send v1 button. You will get a JavaScript error displayed in the console:
Access to fetch at ‘http://localhost:8000/api/v1/messages’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
The message says that the browser has blocked the request because of a CORS policy. It suggests two solutions. The second suggestion is to change the mode
from cors
to no-cors
in the JavaScript fetch request. This is not an option as the browser always deletes the response data when in no-cors
mode to prevent data from being read by an unauthorized client.
The solution to the issue is for the server to set a response header that allows the browser to make cross-domain requests to it.
Access-Control-Allow-Origin: http://localhost:8080
This tells the web browser that the cross-origin requests are to be allowed for the specified domain. If the domain specified in the response header matches the domain of the web page, specified in the Origin
request header, then the browser will not block the response being received by JavaScript.
We are going to set the header when the URL contains v2
. Change the GetMessages()
function in cors/server.go
to the following:
func GetMessages(c *gin.Context) {
version := c.Param("version")
fmt.Println("Version", version)
if version == "v2" {
c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
}
c.JSON(http.StatusOK, gin.H{"messages": messages})
}
This sets a header to allow cross-origin requests for the v2
URI.
Restart the server and go to the web page. If you click on Get v1
you will get blocked by CORS. If you click on Get v2
, the request will be allowed.
A response can only have at most one Access-Control-Allow-Origin
header. The header can only specify only one domain. If the server needs to allow requests from multiple origin domains, it needs to generate an Access-Control-Allow-Origin
response header with the same value as the Origin
request header.
Allowing Access from Any Origin Domain
There is an option to prevent CORS from blocking any domain. This is very popular with developers!
Access-Control-Allow-Origin: *
Be careful when using this option. It will get flagged in a security audit. It may also be in violation of an information security policy, which could have serious consequences!
CORS in Flight
Although we have fixed the main CORS issue, there are some limitations. One of the limitations is that only the HTTP GET, and OPTIONS methods are allowed. The GET and OPTIONS methods are read-only and are considered safe as they don’t modify existing content. The POST, PUT, and DELETE methods can add or change existing content. These are considered unsafe. Let’s see what happens when we make a PUT request.
First of all, add a new form to client/index.html
:
<form id="form2">
<input type="text" id="puttext" name="puttext"/>
<input type="button" value="Put v1" onclick="onPut('v1')"/>
<input type="button" value="Put v2" onclick="onPut('v2')"/>
</form>
This form has a text input and the two send buttons as before that call a new JavaScript function.
Next, add the JavaScript funtion to client/control.js
:
function onPut(version) {
const url = "http://localhost:8000/api/" + version + "/messages/0";
var headers = {}
fetch(url, {
method : "PUT",
mode: 'cors',
headers: headers,
body: new URLSearchParams(new FormData(document.getElementById("form2"))),
})
.then((response) => {
if (!response.ok) {
throw new Error(response.error)
}
return response.json();
})
.then(data => {
document.getElementById('messages').value = data.messages;
})
.catch(function(error) {
document.getElementById('messages').value = error;
});
}
This makes a PUT request sending the form parameters in the request body. Note that the URI ends in /0
. This means that the request is to create or change the message with the identifier 0
.
Next, define a PUT handler in the main()
function of rest/server.go
:
r.PUT("/api/:version/messages/:id", PutMessage)
The message identifier is extracted as a path parameter.
Finally, add the request handler function to rest/server.go
:
func PutMessage(c *gin.Context) {
version := c.Param("version")
id, _ := strconv.Atoi(c.Param("id"))
text := c.PostForm("puttext")
messages[id] = text
if version == "v2" {
c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
}
c.JSON(http.StatusOK, gin.H{"messages": messages})
}
This updates the message from the form data and sends the new list of messages. The function also always sets the allow origin header, as we know that it is required.
Now, restart the servers and load the web page. Make sure that the developer console is open. Add some text to the text input and hit the Send v1
button.
You will see a slightly different CORS error in the console:
Access to fetch at ‘http://localhost:8000/api/v1/messages/0’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
This is saying that a preflight check was made, and that it didn’t set the Access-Control-Allow-Origin
header.
Now, look at the console output from the API server:
[GIN] 2020/12/01 - 11:10:09 | 404 | 447ns | ::1 | OPTIONS "/api/v1/messages/0"
So, what is happening here? JavaScript is trying to make a PUT request. This is not allowed by CORS policy. In the GET example, the browser made the request and blocked the response. In this case, the browser refuses to make the PUT request. Instead, it sent an OPTIONS request to the same URI. It will only send the PUT if the OPTIONS request returns the correct CORS header. This is called a preflight request. As the server doesn’t know what method the OPTIONS preflight request is for, it specifies the method in a request header:
Access-Control-Request-Method: PUT
Let’s fix this by adding a handler for the OPTIONS request that sets the allow origin header in cors/server.go
:
func OptionMessage(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
}
func main() {
messages = append(messages, "Hello CORS!")
r := gin.Default()
r.GET("/api/:version/messages", GetMessages)
r.PUT("/api/:version/messages/:id", PutMessage)
r.OPTIONS("/api/v2/messages/:id", OptionMessage)
r.Run(":8000")
}
Notice that the OPTIONS handler is only set for the v2
URI as we don’t want to fix v1
.
Now, restart the server and send a message using the Put v2
button. We get yet another CORS error!
Access to fetch at ‘http://localhost:8000/api/v2/messages/0’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
This is saying that the preflight check needs another header to stop the PUT request from being blocked.
Add the response header to cors/server.go
:
func OptionMessage(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
c.Header("Access-Control-Allow-Methods", "GET, OPTIONS, POST, PUT")
}
Restart the server and resend the message. The CORS issues are resolved.
What Else Does CORS Block?
CORS has a very restrictive policy regarding which HTTP request headers are allowed. It only allows safe listed request headers. These are Accept
, Accept-Language
, Content-Language
, and Content-Type
. They can only contain printable characters and some punctuation characters are not allowed. Header values can’t have more than 128 characters.
There are further restrictions on the Content-Type
header. It can only be one of application/x-www-form-urlencoded
, multipart/form-data
, and text/plain
. It is interesting to note that application/json
is not allowed.
Let’s see what happens if we send a custom request header. Modify the onPut()
function in frontend/control.js
to set a header:
var headers = { "X-Token": "abcd1234" }
Now, make sure that the servers are running and load or reload the web page. Type in a message and click the Put v1
button. You will see a CORS error in the developer console.
Access to fetch at ‘http://localhost:8000/api/v2/messages/0’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: Request header field x-token is not allowed by Access-Control-Allow-Headers in preflight response.
Any header which is not CORS safe-listed causes a preflight request. This also contains a request header that specifies the header that needs to be allowed:
Access-Control-Request-Headers: x-token
Note that the header name x-token
is specified in lowercase.
The preflight response can allow non-safe-listed headers and can remove restrictions on safe listed headers:
Access-Control-Allow-Headers: X_Token, Content-Type
Next, change the function in cors/server.go
to allow the custom header:
func OptionMessage(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
c.Header("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT")
c.Header("Access-Control-Allow-Headers", "X-Token")
}
Restart the server and resend the message by clicking the Put v2
button. The request should now be successful.
CORS also places restrictions on response headers. There are seven whitelisted response headers: Cache-Control
, Content-Language
, Content-Length
, Content-Type
, Expires
, Last-Modified
, and Pragma
. These are the only response headers that can be accessed from JavaScript. Let’s see this in action.
First of all, add a text area to frontend/index.html
to display the headers:
<textarea id="headers" name="headers" rows="10" cols="50">Headers</textarea><br/>
Next, replace the first then
block in the onPut
function in frontend/control.js
to display the response headers:
.then((response) => {
if (!response.ok) {
throw new Error(response.error)
}
response.headers.forEach(function(val, key) {
document.getElementById('headers').value += 'n' + key + ': ' + val;
});
return response.json();
})
Finally, set a custom header in rest/server.go
:
func PutMessage(c *gin.Context) {
version := c.Param("version")
id, _ := strconv.Atoi(c.Param("id"))
text := c.PostForm("puttext")
messages[id] = text
if version == "v2" {
c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
}
c.Header("X-Custom", "123456789")
c.JSON(http.StatusOK, gin.H{"messages": messages})
}
Now, restart the server and reload the web page. Type in a message and hit Put v2
. You should see some headers displayed, but not the custom header. CORS has blocked it.
This can be fixed by setting another response header to expose the custom header in server/server.go
:
func PutMessage(c *gin.Context) {
version := c.Param("version")
id, _ := strconv.Atoi(c.Param("id"))
text := c.PostForm("puttext")
messages[id] = text
if version == "v2" {
c.Header("Access-Control-Expose-Headers", "X-Custom")
c.Header("Access-Control-Allow-Origin", "http://localhost:8080")
}
c.Header("X-Custom", "123456789")
c.JSON(http.StatusOK, gin.H{"messages": messages})
}
Restart the server and reload the web page. Type in a message and hit Put v2
. You should see some headers displayed, including the custom header. CORS has now allowed it.
Credentials Are a Special Case
There is yet another CORS blocking scenario. JavaScript has a credentials
request mode. This determines how user credentials, such as cookies are handled. The options are:
omit
: Never send or receive cookies.same-origin
: This is the default, that allows user credentials to be sent to the same origin.include
: Send user credentials even if cross-origin.
Let’s see what happens.
Modify the fetch call in the onPut()
function in frontend/Control.js
:
fetch(url, {
method : "PUT",
mode: 'cors',
credentials: 'include',
headers: headers,
body: new URLSearchParams(new FormData(document.getElementById("form2"))),
})
Now, make sure that the client and server are running and reload the web page. Send a message as before. You will get another CORS error in the developer console:
Access to fetch at ‘http://localhost:8000/api/v2/messages/0’ from origin ‘http://localhost:8080’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: The value of the ‘Access-Control-Allow-Credentials’ header in the response is ‘’ which must be ‘true’ when the request’s credentials mode is ‘include’.
The fix for this is to add another header to both the OptionMessage()
, and PutMessage()
functions in cors/server.go
as the header needs to be present in both the preflight request and the actual request:
c.Header("Access-Control-Allow-Credentials", "true")
The request should now work correctly.
The credentials issue can also be resolved on the client by setting the credentials mode to omit
and sending credentials as request parameters, instead of using cookies.
Control CORS Cache Configuration
The is one more CORS header that hasn’t yet been discussed. The browser can cache the preflight request results. The Access-Control-Max-Age
header specifies the maximum time, in seconds, the results can be cached. It should be added to the preflight response headers as it controls how long the Access-Control-Allow-Methods
and Access-Control-Allow-Headers
results can be cached.
Access-Control-Max-Age: 600
Browsers typically have a cap on the maximum time. Setting the time to -1
prevents caching and forces a preflight check on all calls that require it.
How to Prevent CORS Issues with Okta
Authentication providers, such as Okta, need to handle cross-domain requests to their authentication servers and API servers. This is done by providing a list of trusted origins. See Okta Enable CORS for more information.
How CORS Prevents Security Issues
CORS is an important security feature that is designed to prevent JavaScript clients from accessing data from other domains without authorization.
Modern web browsers implement CORS to block cross-domain JavaScript requests by default. The server needs to authorize cross-domain requests by setting the correct response headers.
CORS has several layers. Simply allowing an origin domain only works for a subset of request methods and request headers. Some request methods and headers force a preflight request as a further means of protecting data. The actual request is only allowed if the preflight response headers permit it.
CORS is a major pain point for developers. If a request is blocked, it is not easy to understand why, and how to fix it. An understanding of the nature of the blocked request, and of the CORS mechanisms, can easily overcome such issues.
If you enjoyed this post, you might like related ones on this blog:
- What is the OAuth 2.0 Implicit Grant Type?
- Combat Side-Channel Attacks with Cross-Origin Read Blocking
Follow us for more great content and updates from our team! You can find us on Twitter, Facebook, subscribe to our YouTube Channel or start the conversation below.
November 5, 2018
In this article, we explain what Cross-Origin Resource Sharing (CORS) is and how to avoid errors associated with it and the Access-Control-Allow-Origin header. This includes describing it both from the viewpoint of the frontend and the backend.
Cross-Origin Resource Sharing (CORS) is a mechanism allowing (or disallowing) the resources to be requested from another origin than it is served on. It is built into the browsers and uses HTTP headers to determine whether or not it is safe to allow a cross-origin request. When a web application requests a source with a different origin (origin includes a domain, a protocol, and a port) it is cross-origin. Browsers restrict such requests unless the response from the other origin includes the right headers.
Let’s jump straight into coding. We use a simple Express app here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
const express = require(‘express’); const app = express(); app.get(‘/posts’, function(req, res) { res.send([ { id: 0, title: ‘Lorem ipsum’, content: ‘Dolor sit amet’, author: ‘Marcin’ }, { id: 1, title: ‘Vestibulum cursus’, content: ‘Dante ut sapien mattis’, author: ‘Marcin’ } ]); }); app.listen(8080); |
This, when running locally, opens the
http://localhost:8080/posts endpoint.
So far so good. Now try to use Postman to perform a GET request.
A success! Let’s modify our app a little and add an additional endpoint:
app.get(‘/’, function(req, res) { res.send(); }); |
This will give us an empty document at the
http://localhost:8080 address. Let’s open it in the browser, and in DevTools execute:
fetch(‘http://localhost:8080/posts’); |
Works fine as well!
In this example, we run our request in the same origin, but this is often not the case. Imagine creating a frontend app that runs in a different origin, for example on a different port. To create such a situation, let’s run an additional express app on a different port.
const express = require(‘express’); const app = express(); app.get(‘/’, function(req, res) { res.send(); }); app.listen(4200); |
Now, let’s open our second app at the address
http://localhost:4200 and try to perform the same fetch request.
This results in an error:
Access to fetch at ‘http://localhost:8080/posts’ from origin ‘http://localhost:4200’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
As you can see, the request itself was successful, but the browser blocked it.
Conclusions
- The first example includes us using Postman, so the CORS mechanism wasn’t involved
- In the second example, we perform a request from the same origin, so the CORS mechanism didn’t block our request
- The third example is a Cross-Origin request and therefore it is blocked.
This does not need to be the case. As the error states, we can set the Access-Control-Allow-Origin header. You need to attach it to the response that the browser receives from the server.
Access-Control-Allow-Origin header
To specify what origins have access to the resource, you need to add the Access-Control-Allow-Origin header to your response. It will be interpreted by the browser of the visitor of your site.
While using Express there are a few ways to do that. The simplest one is to attach the header straight in the handler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
app.get(‘/posts’, function(req, res) { res.setHeader(‘Access-Control-Allow-Origin’, ‘http://localhost:4200’); res.send([ { id: 0, title: ‘Lorem ipsum’, content: ‘Dolor sit amet’, author: ‘Marcin’ }, { id: 1, title: ‘Vestibulum cursus’, content: ‘Dante ut sapien mattis’, author: ‘Marcin’ } ]); }); |
This will tell the browser that it is safe to request a resource from that origin.
It works! But that means that you would have to call
setHeader in the every one of your handlers. This is where you can use middleware:
app.use(function (req, res, next) { res.setHeader(‘Access-Control-Allow-Origin’, ‘http://localhost:4200’); next(); }); |
If you call the
use function, the callback you provide will be executed every time a request is sent to the server. It results in attaching the Access-Control-Allow-Origin header to all your responses.
Possible values
One of the possibilities is to specify an exact origin as we did in the previous example. If you choose to be specific, you need to all the way: browsers do not support multiple Access-Control-Allow-Origin headers. On the other hand, you can use a wildcard:
res.setHeader(‘Access-Control-Allow-Origin’, ‘*’);
This value tells the browser that the given resource can be shared with any origin. There is one catch though: when using a wildcard, you can’t send or receive cookies. If you try to do so, the browser throws an error.
Other ways to bypass the CORS policy
One way to override the CORS policy is to install an extension such as Allow-Control-Allow-Origin: *. It Adds the
Allow—Control—Allow—Origin: * header to the all the responses that your browser receives. As mentioned above, it disrupts the way that cookies are sent and received, so keep that in mind.
Another thing you can do is to specify in your request, that you want to bypass the CORS secure mechanism. You can do it if you use Fetch API by setting the mode parameter to no-cors:
fetch(‘http://localhost:8080/posts’, { mode: ‘no-cors’ }); |
This will work regardless of the Access-Control-Allow-Origin header. There is no equivalent of that in the XMLHttpRequest, unfortunately.
If you would like to know more about Fetch API and XMLHttpRequest, check out Comparing working with JSON using the XHR and the Fetch API
Summary
In this article, we explained what the Cross-Origin Resource Sharing (CORS) is, and how can we deal with it in terms of the origin access control. It included creating a simple backend express app and sending requests both from Postman and the browser to illustrate how the CORS works. Aside from that, the article provided tips on how to deal with this error by setting the Access-Control-Allow-Origin header, using browser extensions and an additional parameter that you can set in the Fetch API request. This can help you design both your frontend and backend while keeping the CORS mechanism in mind.
Thanks to that, you can avoid errors like that one:
No ‘Access-Control-Allow-Origin’ header is present on the requested resource.
Previous article
Comparing working with JSON using the XHR and the Fetch API
Next article
Handling errors in JavaScript with try…catch and finally
В ходе использования программного интерфейса приложения (API) XMLHttpRequest скриптового языка браузеров JavaScript, обычно в тандеме с технологией AJAX (Asynchronous JavaScript And XML), могут возникать различные ошибки CORS (Cross-origin resource sharing) при попытке доступа к ресурсам другого домена.
Здесь мы условились о том, что:
- src.example.org — это домен, с которого отправляются XMLHttpRequest кросс-доменные запросы
- target.example.com/example.php — это домен и скрипт /example.php, на который XMLHttpRequest кросс-доменные запросы отправляются
Перечисленные ниже CORS ошибки приводятся в том виде, в котором они выдавались в консоль веб-браузера.
https://target.example.com/example.php в данном примере фактически был размещён на сервере страны отличной от Украины и посредством CURL предоставлял доступ к российскому сервису проверки правописания от Яндекса, — как извесно доступ к сервисам Яндекса из Украины был заблокирован большинством Интернет-провайдеров.
При попытке проверить правописание в редакторе выдавалась ошибка: «The spelling service was not found: (https://target.example.com/example.php)«
Причина: отсутствует заголовок CORS ‘Access-Control-Allow-Origin’
«NetworkError: 403 Forbidden — https://target.example.com/example.php»
example.php
Запрос из постороннего источника заблокирован: Политика одного источника запрещает чтение удаленного ресурса на https://target.example.com/example.php. (Причина: отсутствует заголовок CORS ‘Access-Control-Allow-Origin’).
Запрос из постороннего источника заблокирован: Политика одного источника запрещает чтение удаленного ресурса на https://target.example.com/example.php. (Причина: не удалось выполнить запрос CORS).
Решение: отсутствует заголовок CORS ‘Access-Control-Allow-Origin’
CORS для src.example.org должно быть разрешено на стороне сервера принимающего запросы — это можно сделать в .htaccess следующим образом:
<Files ~ "(example)+.php"> Header set Access-Control-Allow-Origin "https://src.example.org" </Files>
Причина: неудача канала CORS preflight
«NetworkError: 403 Forbidden — https://target.example.com/example.php» example.php
Запрос из постороннего источника заблокирован: Политика одного источника запрещает чтение удаленного ресурса на https://target.example.com/example.php. (Причина: неудача канала CORS preflight).
Запрос из постороннего источника заблокирован: Политика одного источника запрещает чтение удаленного ресурса на https://target.example.com/example.php. (Причина: не удалось выполнить запрос CORS).
Решение: неудача канала CORS preflight
Причины здесь могут быть разные, среди которых может быть запрет некоторых ИП на стороне брандмауэра, либо ограничения на методы запроса в том же .htaccess строкой: «RewriteCond %{REQUEST_METHOD} !^(post|get) [NC,OR]
«.
Во-втором случае это может быть зафиксировано в лог-файле ошибок сервера:
xxx.xxx.xx.xxx [07/May/2018:09:55:15 +0300] «OPTIONS /example.php HTTP/1.1» 403 «Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:52.0) Gecko/20100101 Firefox/52.0» «Referer: -«
Примечание:
Нужно помнить, что ИП-адрес в лог-файле сервера принадлежит клиенту/браузеру, а не удалённому серверу src.example.org на страницах которого в браузере клиента был инициирован CORS (кросс-доменный) запрос!
В случае с .htaccess
строку подправим до такого состояния: «RewriteCond %{REQUEST_METHOD} !^(post|get|options) [NC,OR]
«.
Причина: отсутствует токен ‘x-requested-with’ в заголовке CORS ‘Access-Control-Allow-Headers’ из канала CORS preflight
Запрос из постороннего источника заблокирован: Политика одного источника запрещает чтение удаленного ресурса на https://target.example.com/example.php. (Причина: отсутствует токен ‘x-requested-with’ в заголовке CORS ‘Access-Control-Allow-Headers‘ из канала CORS preflight).
Запрос из постороннего источника заблокирован: Политика одного источника запрещает чтение удаленного ресурса на https://target.example.com/example.php. (Причина: не удалось выполнить запрос CORS).
Решение: отсутствует токен ‘x-requested-with’ в заголовке CORS ‘Access-Control-Allow-Headers’ из канала CORS preflight
Посредством .htaccess
добавим заголовок Access-Control-Allow-Headers:
<Files ~ "(example)+.php"> Header set Access-Control-Allow-Origin "https://src.example.org" Header set Access-Control-Allow-Headers: "X-Requested-With" </Files>
Access-Control-Allow-Origin для множества доменов
Заголовок Access-Control-Allow-Origin допускает установку только одного источника, однако при необходимости разрешения CORS для множества источников в .htaccess можно извратится следующим образом:
##### ---+++ BEGIN MOD HEADERS CONFIG +++--- <IfModule mod_headers.c> <Files ~ "(example)+.php"> #Header set crossDomain: true # ## Allow CORS from one domain #Header set Access-Control-Allow-Origin "https://src.example.org" ## Allow CORS from multiple domain SetEnvIf Origin "http(s)?://(www.)?(example.org|src.example.org)$" AccessControlAllowOrigin=$0 Header set Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin # ## Origin, X-Requested-With, Content-Type, Accept, X-Auth-Token Header set Access-Control-Allow-Headers: "X-Requested-With" # ## "GET, POST" Header set Access-Control-Allow-Methods "POST" # #Header set Access-Control-Allow-Credentials true #Header set Access-Control-Max-Age 60 </Files> </IfModule> ##### ---+++// END MOD HEADERS CONFIG +++---
Cross-Origin Resource Sharing (CORS) is a mechanism that browsers and webviews — like the ones powering Capacitor and Cordova — use to restrict HTTP and HTTPS requests made from scripts to resources in a different origin for security reasons, mainly to protect your user’s data and prevent attacks that would compromise your app.
In order to know if an external origin supports CORS, the server has to send some special headers for the browser to allow the requests.
An origin is the combination of the protocol, domain, and port from which your Ionic app or the external resource is served. For example, apps running in Capacitor have capacitor://localhost
(iOS) or http://localhost
(Android) as their origin.
When the origin where your app is served (e.g. http://localhost:8100
with ionic serve
) and the origin of the resource being requested (e.g. https://api.example.com
) don’t match, the browser’s Same Origin Policy takes effect and CORS is required for the request to be made.
CORS errors are common in web apps when a cross-origin request is made but the server doesn’t return the required headers in the response (is not CORS-enabled):
note
XMLHttpRequest cannot load https://api.example.com. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:8100’ is therefore not allowed access.
Request with preflight
By default, when a web app tries to make a cross-origin request the browser sends a preflight request before the actual request. This preflight request is needed in order to know if the external resource supports CORS and if the actual request can be sent safely, since it may impact user data.
A preflight request is sent by the browser if:
- The method is:
- PUT
- DELETE
- CONNECT
- OPTIONS
- TRACE
- PATCH
- Or if it has a header other than:
- Accept
- Accept-Language
- Content-Language
- Content-Type
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- Or if it has a
Content-Type
header other than:- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
- Or if a
ReadableStream
or event listeners inXMLHttpRequestUpload
are used.
If any of the conditions above are met, a preflight request with the OPTIONS
method is sent to the resource URL.
Let’s suppose we are making a POST
request to a fictional JSON API at https://api.example.com
with a Content-Type
of application/json
. The preflight request would be like this (some default headers omitted for clarity):
OPTIONS / HTTP/1.1
Host: api.example.com
Origin: http://localhost:8100
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type
If the server is CORS enabled, it will parse the Access-Control-Request-*
headers and understand that a POST
request is trying to be made from http://localhost:8100
with a custom Content-Type
.
The server will then respond to this preflight with which origins, methods, and headers are allowed by using the Access-Control-Allow-*
headers:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:8100
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type
If the returned origin and method don’t match the ones from the actual request, or any of the headers used are not allowed, the request will be blocked by the browser and an error will be shown in the console. Otherwise, the request will be made after the preflight.
In our example, since the API expects JSON, all POST
requests will have a Content-Type: application/json
header and always be preflighted.
Simple requests
Some requests are always considered safe to send and don’t need a preflight if they meet all of the following conditions:
- The method is:
- GET
- HEAD
- POST
- Have only these headers:
- Accept
- Accept-Language
- Content-Language
- Content-Type
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- The
Content-Type
header is:- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
- No
ReadableStream
or event listeners inXMLHttpRequestUpload
are used.
In our example API, GET
requests don’t need to be preflighted because no JSON data is being sent, and so the app doesn’t need to use the Content-Type: application/json
header. They will always be simple requests.
Header | Value | Description |
---|---|---|
Access-Control-Allow-Origin | origin or * |
Specifies the origin to be allowed, like http://localhost:8100 or * to allow all origins. |
Access-Control-Allow-Methods | methods |
Which methods are allowed when accessing the resource: GET , HEAD , POST , PUT , DELETE , CONNECT , OPTIONS , TRACE , PATCH . |
Access-Control-Allow-Headers | headers |
Used in response to a preflight request to indicate which headers can be used when making the actual request, aside from the simple headers, which are always allowed. |
Access-Control-Allow-Credentials | true or false |
Whether or not the request can be made with credentials. |
Access-Control-Expose-Headers | headers |
Specifies the headers that the browser is allowed to access. |
Access-Control-Max-Age | seconds |
Indicates how long the results of a preflight request can be cached. |
The browser automatically sends the appropriate headers for CORS in every request to the server, including the preflight requests. Please note that the headers below are for reference only, and should not be set in your app code (the browser will ignore them).
All Requests
Header | Value | Description |
---|---|---|
Origin | origin |
Indicates the origin of the request. |
Preflight Requests
Header | Value | Description |
---|---|---|
Access-Control-Request-Method | method |
Used to let the server know what method will be used when the actual request is made. |
Access-Control-Request-Headers | headers |
Used to let the server know what non-simple headers will be used when the actual request is made. |
A. Enabling CORS in a server you control
The correct and easiest solution is to enable CORS by returning the right response headers from the web server or backend and responding to preflight requests, as it allows to keep using XMLHttpRequest
, fetch
, or abstractions like HttpClient
in Angular.
Ionic apps may be run from different origins, but only one origin can be specified in the Access-Control-Allow-Origin
header. Therefore we recommend checking the value of the Origin
header from the request and reflecting it in the Access-Control-Allow-Origin
header in the response.
Please note that all of the Access-Control-Allow-*
headers have to be sent from the server, and don’t belong in your app code.
Here are some of the origins your Ionic app may be served from:
Capacitor
Platform | Origin |
---|---|
iOS | capacitor://localhost |
Android | http://localhost |
Replace localhost
with your own hostname if you have changed the default in the Capacitor config.
Ionic WebView 3.x plugin on Cordova
Platform | Origin |
---|---|
iOS | ionic://localhost |
Android | http://localhost |
Replace localhost
with your own hostname if you have changed the default in the plugin config.
Ionic WebView 2.x plugin on Cordova
Platform | Origin |
---|---|
iOS | http://localhost:8080 |
Android | http://localhost:8080 |
Replace port 8080
with your own if you have changed the default in the plugin config.
Local development in the browser
Command | Origin |
---|---|
ionic serve |
http://localhost:8100 or http://YOUR_MACHINE_IP:8100 |
npm run start or ng serve |
http://localhost:4200 for Ionic Angular apps. |
Port numbers can be higher if you are serving multiple apps at the same time.
Allowing any origin with Access-Control-Allow-Origin: *
is guaranteed to work in all scenarios but may have security implications — like some CSRF attacks — depending on how the server controls access to resources and use sessions and cookies.
For more information on how to enable CORS in different web and app servers, please check enable-cors.org
CORS can be easily enabled in Express/Connect apps with the cors middleware:
const express = require('express');
const cors = require('cors');
const app = express();
const allowedOrigins = [
'capacitor://localhost',
'ionic://localhost',
'http://localhost',
'http://localhost:8080',
'http://localhost:8100',
];
// Reflect the origin if it's in the allowed list or not defined (cURL, Postman, etc.)
const corsOptions = {
origin: (origin, callback) => {
if (allowedOrigins.includes(origin) || !origin) {
callback(null, true);
} else {
callback(new Error('Origin not allowed by CORS'));
}
},
};
// Enable preflight requests for all routes
app.options('*', cors(corsOptions));
app.get('/', cors(corsOptions), (req, res, next) => {
res.json({ message: 'This route is CORS-enabled for an allowed origin.' });
});
app.listen(3000, () => {
console.log('CORS-enabled web server listening on port 3000');
});
B. Working around CORS in a server you can’t control
Don’t leak your keys!
If you are trying to connect to a 3rd-party API, first check in its documentation that is safe to use it directly from the app (client-side) and that it won’t leak any secret/private keys or credentials, as it’s easy to see them in clear text in Javascript code. Many APIs don’t support CORS on purpose, in order to force developers to use them in the server and protect important information or keys.
1. Native-only apps (iOS/Android)
Use the HTTP plugin from Ionic Native to make the requests natively from outside the webview. Please note that this plugin doesn’t work in the browser, so the development and testing of the app must always be done in a device or simulator going forward.
Usage in Ionic Angular 4
import { Component } from '@angular/core';
import { HTTP } from '@awesome-cordova-plugins/http/ngx';
@Component({
selector: 'app-home',
templateUrl: './home.page.html',
styleUrls: ['./home.page.scss'],
})
export class HomePage {
constructor(private http: HTTP) {}
async getData() {
try {
const url = 'https://api.example.com';
const params = {};
const headers = {};
const response = await this.http.get(url, params, headers);
console.log(response.status);
console.log(JSON.parse(response.data)); // JSON data returned by server
console.log(response.headers);
} catch (error) {
console.error(error.status);
console.error(error.error); // Error message as string
console.error(error.headers);
}
}
}
2. Native + PWAs
Send the requests through an HTTP/HTTPS proxy that bypasses them to the external resources and adds the necessary CORS headers to the responses. This proxy must be trusted or under your control, as it will be intercepting most traffic made by the app.
Also, keep in mind that the browser or webview will not receive the original HTTPS certificates but the one being sent from the proxy if it’s provided. URLs may need to be rewritten in your code in order to use the proxy.
Check cors-anywhere for a Node.js CORS proxy that can be deployed in your own server. Using free hosted CORS proxies in production is not recommended.
C. Disabling CORS or browser web security
Please be aware that CORS exists for a reason (security of user data and to prevent attacks against your app). It’s not possible or advisable to try to disable CORS.
Older webviews like UIWebView
on iOS don’t enforce CORS but are deprecated and are very likely to disappear soon. Modern webviews like iOS WKWebView
or Android WebView
(both used by Capacitor) do enforce CORS and provide huge security and performance improvements.
If you are developing a PWA or testing in the browser, using the --disable-web-security
flag in Google Chrome or an extension to disable CORS is a really bad idea. You will be exposed to all kind of attacks, you can’t ask your users to take the risk, and your app won’t work once in production.
Sources
- CORS Errors in Ionic Apps
- MDN
Consider the following situation: you’re trying to fetch some data from an API on your website using fetch()
but end up with an error.
You open up the console and see either “No Access-Control-Allow-Origin
header is present on the requested resource,” or “The Access-Control-Allow-Origin
header has a value <some_url>
that is not equal to the supplied origin” written in red text, indicating that your request was blocked by CORS policy.
Seem familiar? With over 10,000 questions posted under the cors
tag on StackOverflow, it is one of the most common issues that plague frontend developers and backend developers alike. So, what exactly is the CORS policy and why do we face this error often?
What is Cross-Origin Resource Sharing (CORS)?
Interestingly, this is not an error as we portray it, but rather the expected behavior. Our web browsers enforce the same-origin policy, which restricts resource sharing across different origins. Cross-origin resource sharing, or CORS, is the mechanism through which we can overcome this barrier. To understand CORS, let us first understand the same-origin policy and its need.
The same-origin policy
In simple terms, the same-origin policy is the web version of “don’t talk to strangers” incorporated by the browser.
All modern web browsers available today follow the same-origin policy that restricts how XMLHttpRequest
and fetch
requests from one origin interact with a resource from another origin. What’s an origin, exactly?
It’s the combination of a scheme, domain, and port. The scheme could be HTTP, HTTPS, FTP, or anything else. Similarly, the port can also be any valid port number. Same-origin requests are essentially those requests whose scheme, domain, and port match. Let’s look at the following example.
Assuming our origin is http://localhost:3000
, the requests can be categorized into same-origin or cross-origin requests as follows:
Origin | Request Type | Reason |
---|---|---|
http://localhost:3000/about | Same-origin | The path “/about” is not considered as a part of the origin |
http://localhost:3000/shop/product.html | Same-origin | The path “/shop/product.html” is not considered as a part of the origin |
http://localhost:5000 | Cross-origin | Different port (5000 instead of 3000) |
https://localhost:3000 | Cross-origin | Different scheme (HTTPS instead of HTTP) |
https://blog.logrocket.com | Cross-origin | Different scheme, domain, and port |
This is the reason why your frontend running on http://localhost:3000
cannot make API calls to your server running http://localhost:5000
or any other port when you develop single-page applications (SPAs).
Also, requests from origin https://mywebsite.com
to origin https://api.mywebsite.com
are still considered cross-site requests even though the second origin is a subdomain.
Due to the same-origin policy, the browser will automatically prevent responses from cross-origin requests from being shared with the client. This is great for security reasons! But not all websites are malicious and there are multiple scenarios in which you might need to fetch data from different origins, especially in the modern age of microservice architecture where different applications are hosted on different origins.
This is a great segue for us to deep dive into CORS and learn how to use it in order to allow cross-origin requests.
Allowing cross-site requests with CORS
We’ve established that the browser doesn’t allow resource sharing between different origins, yet there are countless examples where we are able to do so. How? This is where CORS comes into the picture.
CORS is an HTTP header-based protocol that enables resource sharing between different origins. Alongside the HTTP headers, CORS also relies on the browser’s preflight-flight request using the OPTIONS
method for non-simple requests. More on simple and preflight requests later in this article.
Because HTTP headers are the crux of the CORS mechanism, let’s look at these headers and what each of them signifies.
Access-Control-Allow-Origin
The Access-Control-Allow-Origin
response header is perhaps the most important HTTP header set by the CORS mechanism. The value of this header consists of origins that are allowed to access the resources. If this header is not present in the response headers, it means that CORS has not been set up on the server.
If this header is present, its value is checked against the Origin
header of request headers. If the values match, the request will be completed successfully and resources will be shared. Upon mismatch, the browser will respond with a CORS error.
To allow all origins to access the resources in the case of a public API, the Access-Control-Allow-Origin
header can be set to *
on the server. In order to restrict only particular origins to access the resources, the header can be set to the complete domain of the client origin such as https://mywebsite.com
.
Access-Control-Allow-Methods
The Access-Control-Allow-Methods
response header is used to specify the allowed HTTP method or a list of HTTP methods such as GET
, POST
, and PUT
that the server can respond to.
This header is present in the response to pre-flighted requests. If the HTTP method of your request is not present in this list of allowed methods, it will result in a CORS error. This is highly useful when you want to restrict users from modifying the data through POST
, PUT
, PATCH
, or DELETE
requests.
Access-Control-Allow-Headers
The Access-Control-Allow-Headers
response header indicates the list of allowed HTTP headers that your request can have. To support custom headers such as x-auth-token
, you can set up CORS on your server accordingly.
Requests that consist of other headers apart from the allowed headers will result in a CORS error. Similar to the Access-Control-Allow-Methods
header, this header is used in response to pre-flighted requests.
Access-Control-Max-Age
Pre-flighted requests require the browser to first make a request to the server using the OPTIONS
HTTP method. Only after this can the main request be made if it is deemed safe. However, making the OPTIONS
call for each pre-flighted request can be expensive.
To prevent this, the server can respond with the Access-Control-Max-Age
header, allowing the browser to cache the result of pre-flighted requests for a certain amount of time. The value of this header is the amount of time in terms of delta seconds.
More great articles from LogRocket:
- Don’t miss a moment with The Replay, a curated newsletter from LogRocket
- Learn how LogRocket’s Galileo cuts through the noise to proactively resolve issues in your app
- Use React’s useEffect to optimize your application’s performance
- Switch between multiple versions of Node
- Discover how to animate your React app with AnimXYZ
- Explore Tauri, a new framework for building binaries
- Compare NestJS vs. Express.js
Overall, here’s the syntax of how CORS response headers look like:
Access-Control-Allow-Origin: <allowed_origin> | * Access-Control-Allow-Methods: <method> | [<method>] Access-Control-Allow-Headers: <header> | [<header>] Access-Control-Max-Age: <delta-seconds>
Simple requests vs. pre-flighted requests
Requests that do not trigger a CORS preflight fall under the category of simple requests. However, the request has to satisfy some conditions only after it is deemed as a simple request. These conditions are:
- The HTTP method of the request should be one of these:
GET
,POST
, orHEAD
- The request headers should only consist of CORS safe-listed headers such as
Accept
,Accept-Language
,Content-Language
, andContent-Type
apart from the headers automatically set by the user agent - The
Content-Type
header should have only either of these three values:application/x-www-form-urlencoded
,multipart/form-data
, ortext/plain
- No event listeners are registered on the object returned by the
XMLHttpRequest.upload
property if usingXMLHttpRequest
- No
ReadableStream
object should be used in the request
On failing to satisfy either of these conditions, the request is considered to be a pre-flighted request. For such requests, the browser has to first send a request using the OPTIONS
method to the different origin.
This is used to check if the actual request is safe to send to the server. The approval or rejection of the actual request depends on the response headers to the pre-flighted request. If there is a mismatch between these response headers and the main request’s headers, the request is not made.
Enabling CORS
Let’s consider our initial situation where we faced the CORS error. There are multiple ways we could resolve this issue depending on whether we have access to the server on which the resources are hosted. We can narrow it down to two situations:
- You have access to the backend or know the backend developer
- You can manage only the frontend and cannot access the backend server
If you have access to the backend:
Because CORS is just an HTTP header-based mechanism, you can configure the server to respond with appropriate headers in order to enable resource sharing across different origins. Have a look at the CORS headers we discussed above and set the headers accordingly.
For Node.js + Express.js developers, you can install the cors
middleware from npm. Here is a snippet that uses the Express web framework, along with the CORS middleware:
const express = require('express'); const cors = require('cors'); const app = express(); app.use(cors()); app.get('/', (req, res) => { res.send('API running with CORS enabled'); }); app.listen(5000, console.log('Server running on port 5000'));
If you don’t pass an object consisting of CORS configuration, the default configuration will be used, which is equivalent to:
{ "origin": "*", "methods": "GET,HEAD,PUT,PATCH,POST,DELETE", "preflightContinue": false, "optionsSuccessStatus": 204 }
Here is how you could configure CORS on your server which will only allow GET
requests from https://yourwebsite.com
with headers Content-Type
and Authorization
with a 10 minutes preflight cache time:
app.use(cors({ origin: 'https://yourwebsite.com', methods: ['GET'], allowedHeaders: ['Content-Type', 'Authorization'], maxAge: 600 }));
While this code is specific to Express.js and Node.js, the concept remains the same. Using the programming language and framework of your choice, you can manually set the CORS headers with your responses or create a custom middleware for the same.
If you only have access to the frontend:
Quite often, we may not have access to the backend server. For example, a public API. Due to this, we cannot add headers to the response we receive. However, we could use a proxy server that will add the CORS headers to the proxied request.
The cors-anywhere project is a Node.js reverse proxy that can allow us to do the same. The proxy server is available on https://cors-anywhere.herokuapp.com/
, but you can build your own proxy server by cloning the repository and deploying it on a free platform like Heroku or any other desired platform.
In this method, instead of directly making the request to the server like this:
fetch('https://jsonplaceholder.typicode.com/posts');
Simply append the proxy server’s URL to the start of the API’s URL, like so:
fetch('https://cors-anywhere.herokuapp.com/https://jsonplaceholder.typicode.com/posts');
Conclusion
As we learn to appreciate the same-origin policy for its security against cross-site forgery attacks, CORS does seem to make a lot of sense. While the occurrences of the red CORS error messages in the console aren’t going to magically disappear, you are now equipped with the knowledge to tackle these messages irrespective of whether you work on the frontend or the backend.
LogRocket: Full visibility into your web and mobile apps
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Try it for free.
Всем привет!
Меня зовут Радик, я 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!