In my role as a Software Engineering Coach at Multiverse, I've found that cross-origin resource sharing (CORS) is a concept that regularly trips up my apprentices. Let's take a look at the definition from the MDN Web Docs:
Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources.
…
For security reasons, browsers restrict cross-origin HTTP requests initiated from scripts. For example,
XMLHttpRequest
and the Fetch API follow the same-origin policy. This means that a web application using those APIs can only request resources from the same origin the application was loaded from unless the response from other origins includes the right CORS headers.
This means that if you want a resource on your server to be accessible from other origins, you need to set the Access-Control-Allow-Origin
HTTP response header. Without it, developers will see an error if they try to make a request for the resource. It will be similar to the following:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://example.com/. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
Allowing any origin
To allow CORS requests from any origin, you can use the *
wildcard as the value of the Access-Control-Allow-Origin
header. Here's a simple HTTP server that does this in Node.js. Note that you should only do this for public APIs that you want to be accessible to anyone:
const http = require("http");
const port = 8080;
const server = http.createServer(requestListener);
function requestListener(_req, res) {
const statusCode = 200;
const headers = {
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/json",
};
const body = {
code: statusCode,
message: http.STATUS_CODES[statusCode],
};
res.writeHead(statusCode, headers);
res.end(JSON.stringify(body, null, 2));
}
function listeningListener() {
console.log(`Listening on port ${port}`);
}
server.listen(port, listeningListener);
This server sends a 200 OK
response with the following JSON body:
{
"code": 200,
"message": "OK"
}
Most importantly, the Access-Control-Allow-Origin
is set, meaning the server will accept CORS requests from any origin.
Allowing a single origin
To allow CORS requests from a single origin, you can use the origin as the value of the Access-Control-Allow-Origin
header. For example, to allow CORS requests from my website only, you would use https://barker.codes
as the value:
const headers = {
"Access-Control-Allow-Origin": "https://barker.codes",
"Content-Type": "application/json",
};
Note that the origin of a URL is its scheme, hostname, and port. For example, the scheme is https://
, the hostname is barker.codes
, and the port is 443
(servers deliver HTTPS content through port 443 by default, so you don't need to specify it).
The MDN Web Docs explain that if you're using a single origin, then you should also set the Vary
header (see Access-Control-Allow-Origin: CORS and caching):
Suppose the server sends a response with an
Access-Control-Allow-Origin
value with an explicit origin (rather than the "*
" wildcard). In that case, the response should also include aVary
response header with the valueOrigin
— to indicate to browsers that server responses can differ based on the value of theOrigin
request header.
We can easily add this to our headers
object:
const headers = {
"Access-Control-Allow-Origin": "https://barker.codes",
"Content-Type": "application/json",
Vary: "Origin",
};
Allowing a list of origins
We have to do a little more work to allow a list of origins to make CORS requests, as the MDN Web Docs explain (see Access-Control-Allow-Origin: Examples):
Limiting the possible
Access-Control-Allow-Origin
values to a set of allowed origins requires code on the server side to check the value of theOrigin
request header, compare that to a list of allowed origins, and then if theOrigin
value is in the list, set theAccess-Control-Allow-Origin
value to the same value as theOrigin
value.
We can easily modify our requestListener()
function to do this. For example, the following implementation would only allow CORS requests from my website and my friend Chris' website:
function requestListener(req, res) {
const statusCode = 200;
const headers = {
"Content-Type": "application/json",
Vary: "Origin",
};
const body = {
code: statusCode,
message: http.STATUS_CODES[statusCode],
};
const allowedOrigins = new Set([
"https://barker.codes",
"https://gomakethings.com",
]);
if (allowedOrigins.has(req.headers.origin)) {
headers["Access-Control-Allow-Origin"] = req.headers.origin;
}
res.writeHead(statusCode, headers);
res.end(JSON.stringify(body, null, 2));
}
We start with the Content-Type
and Vary
headers. Because our allowed origins must be unique, we store them in a Set
. Then we check if the value of the Origin
request header exists in the Set
, and if so, we use it as the value of the Access-Control-Allow-Origin
header.
Further reading
This post has presented an introduction to CORS using simple GET
requests. There's more to learn about CORS, particularly for HTTP request methods that can cause side effects: browsers have to send a preflight request for these. There are also more CORS headers other than Access-Control-Allow-Origin
. For more information, I suggest you read about CORS in the MDN Web Docs.