What is CORS?
Older Article
This article was published 9 years ago. Some information may be outdated or no longer applicable.
Picture this: you’ve built an application and now you want to consume a REST API. You test with curl or Postman and everything works. Open the browser and nothing. The developer console spits out Failed to load resource: Origin * is not allowed by Access-Control-Allow-Origin. Sound familiar?
Let’s look at what CORS is, when it bites, and what you can do about it.
CORS
CORS stands for ‘Cross-Origin Resources Sharing’. As the name suggests, it governs how resources get shared across different origins.
A simple cross-origin request happens when domain1.com tries to access a resource from domain2.com (an image, a CSS file, anything). This carries security implications, so browsers block cross-origin HTTP requests by default.
When do CORS exceptions happen?
The browser blocks cross-origin resource sharing when you request something from a different domain, protocol, or port than the one you’re making the request from.
This means even local development can trip you up. If your custom REST API runs on http://localhost:3000/api, an Angular application on http://localhost:4200 won’t be able to consume it. Different ports are enough to trigger the block.
For the technically curious, I’d recommend the actual CORS Protocol standard document for the full specification. We’re covering the essentials here.
Preflight request
Browsers automatically fire a ‘preflight’ request as part of a CORS request to check whether the server understands the CORS protocol. It uses the HTTP OPTIONS method. This preflight determines whether the actual request is safe to send.
How to avoid CORS?
That question is a bit misleading (I’ll admit). There’s no way to avoid CORS. It’s baked into the browser’s security.
Some browsers let you disable web security entirely. Chrome accepts a
--disable-web-securityflag, for instance. I’d strongly recommend against this. It kills all web security features, not just CORS. Treat this as something you should never do.
Think server-side code
To get past CORS restrictions, you need to work on the server side. The browser sends a request to a service. If that service isn’t set up to handle CORS requests, the browser can’t do anything except throw the Origin * is not allowed by Access-Control-Allow-Origin error back at you.
Back to our example: a REST API on http://localhost:3000 and an Angular application on http://localhost:4200 trying to consume it.
Depending on how you’ve built the API, there’s almost certainly a package or library that handles CORS for you.
I tend to use Restify for building REST APIs with Node.js. Restify has a package called Restify-CORS-Middleware that does exactly what its name says.
Here’s what it looks like in code:
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`));
The cors variable calls the middleware and accepts an array of origins. Every URL in that array can consume the API without triggering a CORS error.
You could put
http://localhost:*or even*in the origins array. I’d strongly advise against this, especially in production.
Server-side code is not accessible
The above works when you control the API. But what about a closed system where you can’t touch the source code?
Example: you want to pull documents from a NoSQL database via a REST API call. MarkLogic, an Enterprise NoSQL database, exposes a /v1/documents endpoint for exactly this.
Try to hit it from the browser and you’ll get the CORS error. You can’t rewrite the database’s source code just to let your localhost app through.
Proxies
The solution: proxy the requests. Node.js plus the request and http-proxy packages make this quick.
The concept is simple. Create a server, create a proxy server, redirect requests. Here’s the implementation:
// 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!
});
Here’s what’s happening. app.js creates a simple Express web server on port 3000. Its route catches all GET requests (router.route('/*').get...) and forwards them to http://localhost:8000 (MarkLogic’s REST API). So http://localhost:3000/v1/documents?uri=/test.json becomes http://localhost:8000/v1/documents?uri=/test.json.
proxy.js spins up another HTTP server on port 5555. Any request hitting http://localhost:5555 gets forwarded to http://localhost:3000, with an Access-Control-Allow-Origin header tacked on.
Here’s how the Angular side consumes it:
constructor(private http: Http) {
this.http.get('http://localhost:5555/v1/documents?uri=/test.json')
.map(response => response.json())
.subscribe(response => console.log(response));
}
The flow: Angular hits http://localhost:5555/v1/documents?uri=/test.json. The proxy forwards it to http://localhost:3000/v1/documents?uri=/test.json with the CORS header attached. That request then gets translated to http://localhost:8000/v1/documents?uri=/test.json, which pulls the document from the database.
Without these proxies, a direct call to http://localhost:8000/v1/documents?uri=/test.json from the browser would trigger a CORS error. With them in place, the data flows through cleanly.