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 a Vary response header with the value Origin — to indicate to browsers that server responses can differ based on the value of the Origin 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 the Origin request header, compare that to a list of allowed origins, and then if the Origin value is in the list, set the Access-Control-Allow-Origin value to the same value as the Origin 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.