What is CORS?

This post is 4 years old. (Or older!) Code samples may not work, screenshots may be missing and links could be broken. Although some of the content may be relevant please take it with a pinch of salt.

Imagine the following situation: you have just developed an amazing application and now you'd like to consume a REST API. You test the access to the REST API via curl or Postman, but when you open your browser nothing works. You open the browser's developer console and you are presented with Failed to load resource: Origin * is not allowed by Access-Control-Allow-Origin. Bummer. Sounds familiar?

We are going to investigate what CORS is, under what circumstances it's possible to receive such warnings and of course, we'll also see what you can do in order to overcome the issue.

CORS

CORS is an abbreviation that stands for 'Cross-Origin Resources Sharing' and as the name suggests it allows resources to be shared coming from a variety of origins.

A simple cross-origin request would be when domain1.com would be accessing a resource from domain2.com (the resource being an image, a CSS file or anything else). This has some massive security implications of course and the built-in behaviour for browsers is that they'd restrict cross-origin HTTP requests.

When do CORS exceptions happen?

Cross-origin resource sharing is blocked by the user-agent (i.e. the browser) when we are requesting a resource that is on a different domain, using a different protocol or even a different port other than where we are originating our request from.

This can also mean that even if we are running in our local environments and we have our own, custom REST API running locally on http://localhost:3000/api, we won't be able to consume that in an Angular application running on http://localhost:4200. (We are using different ports).

For astute technical readers I recommend the actual CORS Protocal standard document which contains all the technical information on CORS. In this article we are only reviwing the most important aspects.

Preflight request

Browsers automatically issue what is called a 'preflight' request as part of a CORS request to determine if the CORS protocol itself is understood by the server. It uses the HTTP OPTIONS Method. It is this request that determines is the actual request is safe to send to the server.

How to avoid CORS?

The question above is slightly misleading I have to admit, as there's really no way to overcome or to avoid CORS since it is part of the browser's built-in security mechanism.

Some browsers actually allow you to disable web security completely, however I strongly recommend that you do not do this. For example Chrome allows you to pass in the --disable-web-security flag which will disable all web security features, including CORS. Consider this to be bad practice.

Think server-side code

In order to be able to overcome the CORS limitations you first of all need to think at the server-side. Remember, your browser makes the request to a service of some sort and if that service is not configured to be able to handle CORS requests, your browser is not going to be able to do much about this fact, only return you the dreaded Origin * is not allowed by Access-Control-Allow-Origin message.

Let's go back to our previously mentioned example whereby we are developing a REST API locally, running on http://localhost:3000 and have an Angular application running on http://localhost:4200 trying to consume that API running.

Depending how we create the REST API, it's very likely that a package or library exists to help us with enabling CORS requests for our service.

Most of the time I use Restify to create REST APIs using Node.js. Restify has a package called Restify-CORS-Middleware - aptly named - which enables us to handle CORS requests.

Here's how an example code would look like:

const restify = require('restify');
const corsMiddleware = require('restify-cors-middleware');

const cors = corsMiddleware({
origins: ['http://localhost:4200'], // an array of origins
});

const server = restify.createServer();
server.pre(cors.preflight);
server.use(cors.actual);

server.get('/api/products', (request, response) => {
response.json({ message: 'hello REST API' });
});

server.listen(3000, () => console.info(`Magic happens on port 3000`));

Notice the cors variable which calls the middleware and accepts a list of 'origins' in form of an array. Every URL that you put in that array is going to be able to consume this API, without every throwing a CORS error.

You can also put http://localhost:* or even * to the array of origins, however I strongly advise against this - especially in production.

Server-side code is not accessible

The above works if we are the developers behind the API, but what if we are working against a closed-box system, where we can't mess with the source code. Consider this example - we'd like to retrieve documents from a NoSQL database via a REST API call. MarkLogic - an Enterprise NoSQL database - allows us to do exactly this, by accessing the /v1/documents endpoint.

If we attempt to do this, of course we are going to receive the CORS error. And in this case we can't rewrite the source-code of the entire database just to allow a direct request from our test application running on localhost.

Proxies

In such a scenario we need to proxy our requests and we can do this quite easily using Node.js and the packages: request and http-proxy.

The concept is simple - we are going to create a server, a proxy server and redirect requests. Here's the implementation of this using only a few lines of code:

// app.js
const request = require('request');
const express = require('express');
const app = express();
const router = express.Router();

router.route('/*').get(function (req, res) {
const url = req.url;
const marklogicRESTAPI = 'http://localhost:8000';
request(
`${marklogicRESTAPI}${url}`,
{
auth: {
user: 'admin',
pass: 'admin',
sendImmediately: false,
},
},
(error, response, body) => res.end(body)
);
});

app.use('/', router);
app.listen(3000);

// proxy.js
const httpProxy = require('http-proxy');

const proxy = httpProxy
.createServer({
target: 'http://localhost:3000',
})
.listen(5555);

proxy.on('proxyRes', function (proxyReq, req, res, options) {
// add the CORS header to the response
res.setHeader('Access-Control-Allow-Origin', '*'); // never use '*' in production!
});

Let's take a look at what's going on in the two files above. app.js creates a very simple web server using ExpressJS - and this given web server is started up on port 3000.

Notice it's route definition, it essentially takes all GET requests (router.route('/*').get...) and forwards those requests to http://localhost:8000 (which is the REST API host and port that the MarkLogic NoSQL database uses). So this simple forwarding means that a request of http://localhost:3000/v1/documents?uri=/test.json gets forwarded to http://localhost:8000/v1/documents?uri=/test.json.

The second file - proxy.js - add the actual proxying and it very simply fires up yet another HTTP server running on port 5555. This time we say that if a request comes in to http://localhost:5555 forward that request to http://localhost:3000 and also add an Access-Control-Allow-Origin header.

The last piece is to see how we can consume this service that we have created. Let's take a look at the appropriate line from Angular:

constructor(private http: Http) {
this.http.get('http://localhost:5555/v1/documents?uri=/test.json')
.map(response => response.json())
.subscribe(response => console.log(response));
}

So the flow is that Angular requests http://localhost:5555/v1/documents?uri=/test.json, which hits our proxy and the request gets proxied to http://localhost:3000/v1/documents?uri=/test.json but now with the Access-Control-Allow-Origin - and finally this request gets translated to http://localhost:8000/v1/documents?uri=/test.json which is the final call that we needed to return the document from the database.

Without these seemingly complex and intertwined HTTP calls (i.e. making a direct call to http://localhost:8000/v1/documents?uri=/test.json) would have resulted in a CORS error in our Angular application in the browser but having these proxies in place means that we can easily retrieve the data without having to worry about CORS.