Why Do You Need CORS?
Web developers encounter the Same Origin Policy when they need to use resources in more than a single domain. Modern applications leverage CDNs, APIs and services hosted on multiple domains, so developers often need to be able to bypass the Same Origin Policy restrictions. Developers working with nodeJS and other JavaScript frameworks may need to expose resources in an application to code running on on other domains. Cross-Origin Resource Sharing (CORS) is a recommendation by The World Wide Web Consortium (W3C) which has been partly implemented by modern browsers: https://www.w3.org/TR/cors/
CORS provides a method to use resources across domains using HTTP headers for a handshake that vets the domain making the request as an allowed domain.
There is a "CORS Enabled" wiki maintained by the W3C with up-to-date information about CORS support: https://www.w3.org/wiki/CORS_Enabled
The Same Origin Policy is defined by the Internet Engineering Task Force (IETF) https://tools.ietf.org/html/rfc6454#page-4. The Same Origin Policy protects sensitive data, for example personal or application information stored in cookies. The Same Origin Policy also applies to XMLHttpRequest (XHR) - you can't make a cross-domain XHR request unless the target domain provides an Access-Control-Allow-Origin header allowing the domain making the request.
DOM Elements Allowed For Cross-Origin Sharing
You probably know that many DOM elements may be shared across domains freely:
- Images with the
tag, as long as the file type matches expected image formats. - Media files with the
and
tags as long as the file type matches expected media formats
- JavaScript with the
tag.
- CSS with the
tag. Cross-origin CSS requires a correct Content-Type header. Restrictions vary by browser.
- Plug-ins with the
and
tags.
- Fonts with @font-face. Support for this method varies by client browser.
- Any content or URI loaded with the
and
tags. Note that the X-Frame-Options header can prevent cross-origin interaction between frames.
Pretty much anything else is restricted by the Same Origin Policy.
CORS Basics: the Access-Control-Allow-Origin Header, the XMLHttpRequest, and the Response
First we need to understand some CORS basics.
The Access-Control-Allow-Origin Header
The Access-Control-Allow-Origin header allows CORS access from the requesting domain. The header specifying the allowed domain may be added with PHP code, or any other method used to send headers, at the endpoint URI:
Although the W3C recommendation proposes allowing a list of domains, that feature is not yet supported by most browsers. If you need to allow access from multiple domains, the wildcard * may be used. It is recommended that you add additional logic in your code to limit the domains that can actually receive a response before sending the response.
The CORS XMLHttpRequest
Create a function that makes the XHR request and tests the browser for CORS support:
function CORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr) {
// XHR uses withCredentials in modern Chrome/Firefox/Opera/Safari
// and IE >= 10
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined") {
// XDomainRequest is used in IE <= 9 instead of withCredentials
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
// neither withCredentials nor XDomainRequest is returned
// CORS not supported, return a null xhr object
xhr = null;
}
return xhr;
}
You may give the function and the variable "xhr" names of your choice.
Passing A Successful Request to A Function
With the request defined in the preceding code, this does something with the XHR GET request when it achieves the ready state. Note that if CORS isn't supported and xhr = null, or the request fails, nothing will will be done with it. The request will fail if the Access-Control-Allow-Origin header does not match the domain making the request.
var request = CORSRequest("get", "http://somedomain.com/");
if (request){
request.onload = function(){
// do something with the request.responseText
};
request.send();
}
Additional Headers Are Required for Preflight
This example is a simple GET request and requires only the Access-Control-Allow-Origin header. Additional headers are required for Preflight when the request:
- Uses methods other than GET, POST, or HEAD
- Has custom headers
- Has a Content-Type other than text/plain, application/x-www-form-urlencoded, or multipart/form-data
The W3C recommendation explains Preflight: https://www.w3.org/TR/cors/#resource-preflight-requests
JS Frameworks Implement CORS in Varying Ways
The CORS requests and headers are defined by the W3C, but implementation and code syntax can vary across frameworks. We will be looking at CORS requests in some of the frameworks that typically run on a nodeJS server: Express.js, AngularJS, Backbone.js, Ember.js, and also in Socket.IO.
CORS Code Examples in JS Frameworks
Now that we understand how CORS requests are allowed using HTTP headers, we can learn how CORS requests are handled in a few popular JavaScript frameworks.
CORS in Express.js
Express.js is the most popular server framework for NodeJS.
You can allow CORS on one or more routes in your Express application with code that sets CORS headers. CORS in Express also requires setting the Access-Control-Allow-Headers header, in addition to the Access-Control-Allow-Origin header.
This example adds the Access-Control-Allow-Origin header and the Access-Control-Allow-Headers headers globally for all requests on all routes.
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
next();
});
Security Considerations
You should limit the allowed origin to a single domain instead of the wildcard * for better security, or add code to check the domain(s) that are allowed to make a request to your app when using the wildcard. Also, you can limit sending the headers only on routes that require CORS by replacing app.all with a more specific route and method.
For example, the following code sends the CORS headers on a GET request on the route /user, and only allows the request from http://www.somedomain.com
.
app.get('/user', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "http://www.somedomain.com");
res.header("Access-Control-Allow-Headers", "X-Requested-With");
next();
});
Since this is JavaScript code, you may dynamically manage the values of routes, methods, and domains via variables, instead of hard-coding the values.
Using CORS NPM in Express
You can use npm (Node Package Manager) to install a package that enables CORS in Express with Connect.js:
npm install cors
The cors package offers flexible options which implement features of the CORS specification, including Preflight. It provides methods to validate an origin domain dynamically using a function or a regular expression, and handler functions to process Preflight.
CORS NPM Configuration Options
Without setting any values, the default configuration allows all origins and methods without Preflight. CORS requests other than GET, HEAD, POST will fail without Preflight. We will soon see how to enable Preflight.
The default configuration:
{
"origin": "*",
"methods": "GET,HEAD,PUT,PATCH,POST,DELETE",
"preflightContinue": false
}
These are the configuration option for cors npm:
origin: sets the Access-Control-Allow-Origin header with a string, regular expression, or an array of strings and/or regex containing the full URL and protocol making the request
- TRUE is the default value for origin. CORS is enabled. req.header('Origin') contains the origin domain.
- When origin is set to FALSE CORS is disabled.
- origin can be set to a function with the request origin as the first parameter and a callback function as the second parameter.
- The value of origin can be a string, example:
"http://somedomain.com"
- The value of origin can be a regular expression.
- The value of origin can be an array of regular expressions and/or strings to match to the requesting origin.
methods: sets the Access-Control-Allow-Methods header.
- A comma-delimited string of HTTP methods, example: 'GET,POST'
- An array of HTTP methods, example: ['GET', 'PUT', 'POST']
allowedHeaders: Sets the Access-Control-Allow-Headers header.
- A comma-delimited string of allowed headers, example: “Content-Type,Authorization"
- An array of allowed headers, example: ['Content-Type', 'Authorization']
- If allowedHeaders is unspecified, the default value comes from the request's Access-Control-Request-Headers header
exposedHeaders: Sets the Access-Control-Expose-Headers header.
- A comma-delimited string of exposed headers, example: 'Content-Range,X-Content-Range'
- An array of exposed headers, example: ['Content-Range', 'X-Content-Range']
- If exposedHeaders is unspecified, no custom headers are exposed
credentials: Sets the Access-Control-Allow-Credentials header. The withCredentials header is useful when authentication is needed using oAuth or other methods. Authentication is handled during Preflight.
- When credentials is set to TRUE, the header is sent for Preflight
- When credentials is set to FALSE or unspecified, the header is not sent, and Preflight is not allowed.
maxAge: Sets the Access-Control-Allow-Max-Age header.
- TTL in milliseconds to cache the request
- If maxage is unspecified, the request is not cached
preflightContinue: the CORS preflight response is passed to the next handler.
CORS NPM Code Examples
CORS npm may be configured for simple or complex requests. The express and cors packages are always required.
Enable CORS Globally for All Origins and All Routes
This example defines a GET request for route /product/:id. CORS is enabled for all origins and configures the app uses CORS for all routes.
var express = require('express')
, cors = require('cors')
, app = express();
app.use(cors()); // use CORS for all requests and all routes
app.get('/product/:id', function(req, res, next){
res.json({msg: 'response message'});
});
Allow CORS for Dynamic Origins for a Specific Route
Use corsOptions to set the allowed origin(s) with a callback function. corsOptions is passed to the route product/:id which is the only route that has CORS enabled. Changing the value of the variable “whitelist” can dynamically change the allowed origins.
var express = require('express')
, cors = require('cors')
, app = express();
// specify the allowed domains and set corsOptions to check them
var whitelist = ['http://somedomain.com','http://somedomain-other.com'];
var corsOptions = {
origin: function(origin, callback){
var originWhitelisted = whitelist.indexOf(origin) !== -1;
callback(null, originWhitelisted);
}
};
// add corsOptions to the route /product/:id for a GET request
app.get('/product/:id', cors(corsOptions), function(req, res, next)
{
res.json({msg: 'response message'});
});
Different CORS options can be specified for specific routes, or sets of routes, by defining the options with specific variable names. In the above example we set the options for corsOptions, and a different configuration can be stored in another variable. Pass the configuration variable to each route that requires that set of options.
How To Enable Preflight
CORS requests that use a HTTP method other than GET, HEAD, POST (for example DELETE), or require authentication using withCredentials, or that use custom headers, must have a Preflight request before continuing with the CORS requests. Add OPTIONS to the route /products/:id to enable Preflight:
var express = require('express')
, cors = require('cors')
, app = express();
// add OPTIONS to the route /products/:id
app.options('/products/:id', cors());
// use OPTIONS for DELETE
app.del('/products/:id', cors(), function(req, res, next){
res.json({msg: 'response message'});
});
You can enable Preflight globally on all routes with the wildcard:
app.options('*', cors());
Configuring CORS Asynchronously
NodeJS frameworks like Express can handle tasks asynchronously. We can add a callback function we name corsDelegateOptions to the cors parameter passed to the route. The callback function can handle multiple requests asynchronously.
var express = require('express')
, cors = require('cors')
, app = express();
// the allowed origins
var whitelist = ['http://example1.com', 'http://example2.com'];
// the callback function
var corsDelegateOptions = function(req, callback){
var corsOptions;
if(whitelist.indexOf(req.header('Origin')) !== -1){
// the origin matches and is allowed
corsOptions = { origin: true };
}else{
// the origin doesn't match, CORS is disabled
corsOptions = { origin: false };
}
callback(null, corsOptions);
};
// add the callback function
app.get('/products/:id', cors(corsDelegateOptions), function(req, res, next)
{
res.json({msg: 'response message'});
});
CORS in AngularJS
Since Angular is a client-side application, you can't make a CORS request to an Angular application running in the client browser.
You can make requests from an Angular application for resources on other domains.
When the Angular application is running on a JavaScript server, you can use CORS in the server application before sending the data to Angular.
Isomorphic JavaScript runs the same codebase on the server and in the client browser. Typically the server runs on NodeJS. In an Isomorphic JavaScript application using Angular on the front-end, CORS requests are handled by the server.
How To Enable CORS in Angular
We will create a service named "appName" to implement CORS. The $httpProvider.defaults have useXDomain set to true, so it is compatible with Internet Explorer <= 9.
var appName = angular.module('appName', ['appNameApiService']);
appName.config(['$httpProvider', function($httpProvider)
{
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}
]);
Make A CORS Request In Angular
CORS in Angular can be a HTTP request, or a resource request. We will use GET as a HTTP request, and return data about user with id “xyz” as a resource request. You can rename ModuleName and corsControllerName.
angular.module("ModuleName", ['ngResource']).controller('corsControllerName', function ($scope, $http, $resource) {
$http.defaults.useXDomain = true;
// HTTP GET request
$scope.useHttp = function() {
$http.get('http://somedomain.com')
.success(function(data) {
// do something with the data
alert(data.ok);
});
};
// User resource request
$scope.useResource = function() {
var User = $resource('http://somedomain.com', {
userId: '@id'
});
User.get({
userId: 'xyz'
}, function(data) {
// do something with the data
alert(data.ok);
});
};
});
CORS in Backbone.js
Backbone also relies on a separate server, typically provided by NodeJS.
You need to modify Backbone.sync, which handles HTTP requests, to add the cross-domain options. Add the withCredentials property to Backbone.sync for full access to resources that are controlled by the Same Origin Policy.
Backbone.sync uses its own syntax for CRUD operations representing REST methods:
- create: POST /collection
- read: GET /collection[/id]
- update: PUT /collection/id
- patch: PATCH /collection/id
- delete: DELETE /collection/id
Using the Backbone.CrossDomain Extension
The Backbone.CrossDomain extension https://github.com/victorquinn/Backbone.CrossDomain is a quick and easy way to add CORs support. It adds CORS support to Backbone.sync with XHR for clients that support it, and uses with the XDomainRequest Object for IE 7-9. Install Backbone.CrossDomain with npm or bower. If you are using Backbone with the RequireJS module loader, it can be loaded with the Asynchronous Module Definition (AMD) API.
Manually Proxy Backbone.sync For CORS
Without using Backbone.CrossDomain, the following code enables CORS in Backbone.sync with options.crossdomain = true and options.xhrFields = {withCredentials:true}
var corsSync= Backbone.sync;
Backbone.sync = function(method, model, options) {
options || (options = {});
// enable crossDomain
if (!options.crossDomain) {
options.crossDomain = true;
}
// options.xhrFields has withCredentials:true
if (!options.xhrFields) {
options.xhrFields = {withCredentials:true};
}
return corsSync(method, model, options);
};
Using The XHR Header for Ajax in jQuery
If your application has changed the default jQuery header "X-Requested-With: XMLHttpRequest", then CORS requests made with jQuery can fail. Use $.ajaxSetup to make sure the header is set:
$.ajaxSetup({
headers: { 'X-Requested-With' : 'XMLHttpRequest' }
});
Ember Uses jQuery AJAX for CORS
In this example, both crossDomain and withCredentials are enabled in Ember.
$.ajaxSetup({
crossDomain: true,
xhrFields: {
withCredentials: true
}
});
Socket.IO Manages Origins for Security
In Socket.IO the allowed origin(s) need to be explicitly set. This example parses the origin to match protocol, host name, and port:
var origin = request.headers.origin || request.headers.referer
, origins = this.get('origins');
var parts = url.parse(origin);
parts.port = parts.port || 80;
var ok =
~origins.indexOf(parts.hostname + ':' + parts.port) ||
~origins.indexOf(parts.hostname + ':*') ||
~origins.indexOf('*:' + parts.port);
The next example allows three origin domains. The wildcard * can be used to allow any port number, or the port number can be explicit.
var io = require('socket.io')(server,
{origins:'https://somedomain1.com:* http://somedomain2.com:8080 http://www.xomedomain3.com:*'});
Alternatives To CORS
There are other ways to make cross-domain requesests without using CORS: JSON-P, WebSocket, and window.postMessage are not subject to the Same Origin Policy. However, using them in your application may create security risks. CORS provides better security by exchanging headers, and exposing the request response to allow for security sanity checks.
You can also set up a proxy server to intermediate between domains, but that adds latency to the application. CORS allows a direct request without a proxy.
Summary
JavaScript frameworks are evolving rapidly. Always read the project documentation for the latest methods and syntax.
Some JS frameworks have specific syntax for implementing CORS, and also utility packages, extensions and plugins. They all follow the CORS principles of specifying allowed origin(s) and method(s) using HTTP headers. More robust implementations allow custom headers such as withCredentials (useful for oAuth), and Preflight when required for complex CORS requests.
JavaScript frameworks may depend on the jQuery AJAX XHR object, which must be configured properly to allow Cross-Origin requests.
You can create your own methods by referring to the W3C CORS recommendation. The CORS Enabled wiki provides information about CORS support in general.
Keep in mind that not all features in the W3C recommendation are implemented in all client browsers. An important gotcha is that you cannot provide a list of allowed domains in plain CORS: you have a choice of allowing a single domain, or all domains with the wildcard. For better security when using the wildcard, add some code to check the requesting domain against a list of allowed domains or patterns. Some of the examples in this article can handle a list of allowed domains within a specific framework.