CORS

SHARE ON

This blog post is part of the Mixmax 2016 Advent Calendar. The previous post on December 3rd was about Node 6 features.

The Error

XMLHttpRequest cannot load https://contacts-local.mixmax.com/api/contactgroups?user=engtestuser4%40mixmax.com&expand=userId. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://app-local.mixmax.com' is therefore not allowed access. The response had HTTP status code 403.

This error is probably why you are here. You are looking for a fix. Or maybe you’re just curious to learn more about CORS. Whatever the reason, you’ve come to the right place. Read on to learn more about CORS or skip to the end to figure out how to fix your dilemma.

What is CORS?

CORS or Cross-Origin Resource Sharing is a method for allowing a web page to access resources outside the domain from which the page is being loaded. You might ask why this is needed; why can’t I just load arbitrary JavaScript from random places on the internet? One good reason is cookies.

When a browser makes a request to a server in a domain, it sends the cookies that it has for that domain. This means that if I am logged into www.myUnsecureBank.com and I load www.myMaliciousWebsite.com in another tab, the malicious website can fetch data from my bank on my behalf. This works because the malicious site can contain JavaScript that makes AJAX requests outside the malicious website’s domain and the browser will happily send cookies with that request. So the malicious website can call the www.myUnsecureBank.com/api/myBankDetails and the server will respond because the browser will send the cookies that identify you as being logged in.

Fortunately, because of SOP or Same Origin Policy, the browser will block this outbound request. This is where CORS comes in. CORS is a method that will allow us to make these types of requests, under the right circumstances.

How does it work?

Essentially, there are two types of cross origin requests: Simple and Preflighted. Simple requests are typically GET, HEAD, and POST requests with specific headers and allowed values for Content-Type. You can learn more about them here. Preflighted requests are, well, any request that is not a Simple Request (we will learn more about why they are called preflighted in a bit).

For a Simple request, the browser will send the request with an Origin header and the server will reply with an Access-Control-Allow-Origin header. If the two don’t match, the browser will produce a CORS error and not allow the request to complete.

For a Preflighted request, the browser will send an OPTIONS request, before making the main request (which is why it is called a preflighted request). The server will reply with Access-Control-Allow-Origin but also with a Access-Control-Allow-Methods header. This header specifies the type of requests the client is able to make (POST, PATCH, …). If the main request does not match both headers, then the browser will produce a CORS error and not make main the request.

How do I play by the rules?

In order to make CORS work, you need server side changes. For simple requests, you simply have to make your server respond with the appropriate Access-Control-Allow-Origin header.

For preflighted requests, however, you have to also respond with the correct Access-Control-Allow-Methods header for the browser’s OPTIONS request. At Mixmax we like to use the cors package for handling these.

We have a Contacts service that runs under the domain contacts.mixmax.com. We also have our main web app which runs under the domain app.mixmax.com. Because our App makes all types of requests to our Contacts service, we have to handle CORS on the Contacts side.

This is what our router looks like on our Contacts service:

var cors = require('cors'); // Import the CORS library
var express = require('express');
var router = express.Router();

// Configure CORS for our own services
var mixmaxCors = cors({
  origin: function(origin, callback) {
    // if origin is a subdomain of mixmax.com then allow the request
    var originIsWhitelisted = /[^.s]+.mixmax.com$/.test(origin);
    callback(null, originIsWhitelisted);
  },
  // credentials = true is required for cookies to be passed along
  credentials: true
});

// Define the OPTIONS route that will be preflighted by the browser
router.options('/api/contactgroups/?*', mixmaxCors);
// Define the actual API routes
router.use('/api/contactgroups', require('./api/contactgroups'));

Conclusion

CORS is a feature, not a problem. It allows the modern web to exist by allowing web applications to use all sorts of APIs from different domains. Next time you see The Error, think about how it brings safety to your users and how CORS is an elegant way to handle the situation.

Enjoy building elegant APIs? Drop us a line.

SHARE ON

Written By

Jeff Wear

Jeff Wear

From Your Friends At