Home Web Development CORS Essentials

CORS Essentials

By Randall Goya , Rajesh Gunasundaram
books-svg-icon Book
eBook $29.99 $20.98
Print $38.99 $22.99
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
eBook $29.99 $20.98
Print $38.99 $22.99
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
  1. Free Chapter
    Why You Need CORS
About this book
This book explains how to use CORS, including specific implementations for platforms such as Drupal, WordPress, IIS Server, ASP.NET, JBoss, Windows Azure, and Salesforce, as well as how to use CORS in the Cloud on Amazon AWS, YouTube, Mulesoft, and others. It examines limitations, security risks, and alternatives to CORS. It explores the W3C Specification and major developer documentation sources about CORS. It attempts to predict what kinds of extension to the CORS specification, or completely new techniques, will come in the future to address the limitations of CORS Web developers will learn how to share code and assets across domains with CORS. They will learn a variety of techniques that are rather similar in their method and syntax. The book is organized by similar types of framework and application, so it can be used as a reference. Developers will learn about special cases, such as when a proxy is necessary. And they will learn about some alternative techniques that achieve similar goals, and when they may be preferable to using CORS
Publication date:
May 2017
Publisher
Packt
Pages
144
ISBN
9781784393779

 

Chapter 1. Why You Need CORS

In this chapter, you will learn about the following:

  • The same-origin policy that limits sharing resources across domains

  • Granting access to CORS requests by setting headers

  • How to do something with the responseText request from a CORS request

  • Rudimentary security in CORS and ways to add more security

  • Preflight requests to prepare for some types of CORS methods and events

  • Enabling the crossorigin attribute in a script tag for better troubleshooting

  • Alternatives to CORS: JSON-P, WebSockets, and window.postMessage

 

The same-origin policy


Sooner or later, web developers run up against the same-origin policy. Maybe you want to trigger a script on one domain and use the results on a different domain, but you can't.

The same-origin policy is necessary for web application security. The execution of a script may expose sensitive information. Access to this information is limited to the same domain where the script is located, unless access for an external domain has been specifically allowed by code.

Note

The same-origin policy is defined by the Internet Engineering Task Force (IETF) (https://tools.ietf.org/html/rfc6454#page-4).

A major motivation for implementing the same-origin policy is to protect sensitive information stored in cookies from being exposed to another domain. Web applications maintain authenticated user sessions in cookies. The user's personalizations and account information are stored in cookies. To ensure data confidentiality, cookies may not be shared across domains. For cookies, the same origin is shared by the domain or a sub-domain of that domain. For DOM elements such as scripts, the restrictions are more fine-grained.

The same-origin policy also applies to requests made with XMLHttpRequest (XHR). We will see how the Access-Control-Allow-Origin header facilitates the bending of the same-origin policy.

Notably, JSON-P, WebSocket, and window.postMessage are not restricted by the same-origin policy.

 

Considering the origin of entities


Access to DOM elements is allowed only when the request scheme, hostname, and port number match those of the current URI. A subdomain cannot share DOM elements with the parent domain.

  • Scheme in web applications is typically http:// or https://

  • Hostname is typically the domain name plus TLD, or the unique IP address

  • Port number:

    • Typically, port 80 is implicit in http://

    • 443 for SSL over https://

If the Scheme, Hostname, and port number do not match the DOM element, then resource sharing is prohibited as they do not share the same origin. Considering the domain http://www.example.com, the following table provides various combinations of matching and mismatching origins:

URI

Match?

Reason

http://www.example.com/dir1/page1.html

Match

Same protocol and host

http://username:password@www.example.com/dir2/otherpage.html

Match

Same protocol and host

http://example.com/dir/page1.html

Mismatch

Different host (www is a subdomain)

https://www.example.com/dir/page1html

Mismatch

Different protocol(https://)

http://www.example.com:81/dir/page1.html

Mismatch

Same protocol and host but different port (81)

http://en.example.com/dir/page1.html

Mismatch

Different host (en is a subdomain)

Internet Explorer exception policy

Internet Explorer (IE) implements two major differences when it comes to the same-origin policy:

  • IE Trust Zones allow different domains: If both domains are in a highly trusted zone, then the same-origin policy limitations are not applied.

  • Port is ignored: IE ignores the port in same origin components. These URIs are considered from the same origin:

    • http://www.example.com:80/dir/page1.html

    • http://www.example.com:81/dir/page1.html

      Tip

      These exceptions in Internet Explorer are non-standard and are not supported in other browsers. If an application is only viewed in Windows RT mobile or Internet Explorer, then these exceptions could be useful.

 

Commonly allowed cross-origin resource sharing


The same-origin policy is not required for many resources that may be embedded in cross-origin. The sharing of specific file types is limited by file type headers and ensuring that the file extensions and file meta data match the expected type.

DOM elements allowed for cross-origin sharing

The following information box displays scenarios where DOM elements are allowed for cross-origin sharing:

Note

Images with the <img> tag, as long as the file type matches expected image formats.

Media files with the <video> and <audio> tags as long as the file type matches expected media formats.

JavaScript with the <script src="..."></script> tag. This method is used by many third-party applications, which embed a script to act upon the local resources, for example, a social media sharing service that analyzes the shareable images and other assets on current page and reads the URI.

CSS with the <link rel="stylesheet" href="..."> tag. Cross-origin CSS requires a correct content-type header. Client.

Plugins with the <applet>, <object> and <embed> tags.

Fonts with @font-face. Support for this method varies by client browser.

Any content or URI loaded with the <frame> and <iframe> tags.

Tip

The X-Frame-Options header can prevent interaction between frames on different domains.

Allowing cross-origin sharing in WebSockets

The ability of WebSockets to bypass the same-origin policy is seen as a security risk. Using WebSockets on a gateway/server that supports origin-based security provides header-based security similar to CORS.

Limited cross-origin JavaScript API access

JavaScript APIs, such as iframe.contentWindow, window.parent, window.open, and window.opener, provide limited cross-origin access to the Window and Location objects. Some browsers permit access to more properties than the specification allows. You can use window.postMessage instead to communicate between documents in separate windows.

 

Permissions required by JavaScript


Let's consider content scraping. You can write a content scraping script that reads the rendered DOM of an external URI and creates local DOM elements with the same content, without any special configurations.

But what if you first need to run a script on the external URI, for example, to find out whether the user is the same as on your local site? You cannot trigger that external script and return the results without cross-origin sharing via CORS or a similar method to get around the same-origin policy.

JavaScript data storage access is strictly limited by origin

JavaScript data stored in the browser as Local Storage, or in IndexedDB, is separated by origin. Each origin has distinct storage, and JavaScript in one origin cannot read from or written to storage belonging to another origin unless it is given explicit access to a script on another domain by CORS or a similar method.

 

How CORS works – the header and the request


Note

CORS is a specification of World Wide Web Consortium (W3C) http://www.w3.org/TR/cors/.

Cross Origin Resource Sharing (CORS) is allowed by having a header on the target domain where your local domain needs access. Local domains whitelisted in the allow-origin header can now send an XMLHttpRequest (XHR) request or other types of request to the target domain and receive a response.

Note

Local domain and target domain explained

In this book, we will refer to the domains in a CORS request as follows:

  • Local domain (localdomain.com): The domain making the CORS request

  • Target domain (targetdomain.com): The domain receiving the CORS request, hence the target of the request

The CORS header

The CORS header whitelists access to one domain or any domain with the wildcard *. This header allows access from only one domain—the one specified:

Access-Control-Allow-Origin: http://localdomain.com

To enable access from multiple domains, a wild card is used:

Access-Control-Allow-Origin: *

Tip

Not being able to whitelist a list of allowed domains is a major complaint about CORS. Although a list of allowed domains is part of the W3C specification, in practical terms, it is not supported by browsers. (http://www.w3.org/TR/cors/#list-of-origins)

You can specify a single allowed domain or allow from all. The wildcard opens the target site to possible security risks because any domain is allowed to send a cross-origin request to the target domain.

Some authentication/authorization must be added outside of the CORS code to provide security, particularly when using the wildcard.

Example 1 – CORS request with JavaScript

The following example explains the CORS syntax, without actually doing anything with the responseText request:

  • Create the XMLHttpRequest object:

    // Create the XHR object.
    function createCORSRequest(method, url) {
      var xhr = new XMLHttpRequest();
      if ("withCredentials" in xhr) {
        // XHR for Chrome/Firefox/Opera/Safari and IE >= 10
        xhr.open(method, url, true);
      } else if (typeof XDomainRequest != "undefined") {
        // XDomainRequest for IE <= 9
        xhr = new XDomainRequest();
        xhr.open(method, url);
      } else {
        // CORS not supported
        xhr = null;
      }
      return xhr;
    }
  • The createCORSRequest function does the following:

    1. Defines the new XMLHttpRequest request as the variable "xhr".

    2. Checks whether the browser supports CORS via XHR by detecting the withCredentials or XDomainRequest properties.

    3. Opens the request for a resource on the target domain.

  • These are the parameters passed to createCORSRequest(method, url):

    • The method would typically be GET, POST, or another method

    • The URL is the URI of the resource requested by the local domain

IE 10 supports the working draft XMLHttpRequest level 2. Therefore, the withCredentials property can be used to detect CORS support in most browsers, including IE >= 10. To provide backwards compatibility for IE < 10, use its XDomainRequest property.

Tip

Microsoft Internet Explorer 9 makes up 9.14% of desktop browsers, so we must include a fallback check for its the XDomainRequest property when withCredentials fails.

Passing a request to a utility function

var request = createCORSRequest("get", "targetdomain.com/");
if (request){
    request.onload = function(){
        //do something with request.responseText
    };
    request.send();
}

This request does two things:

  • If createCORSRequest() returns an XHR object, it sends the request

  • When the XHR ready state is loaded--request.onload--do something with request.responseText

Example 2: the CORS transaction to retrieve the title tag

This CORS request retrieves the page title from the target domain, targetdomain.com. It parses the responseText request to get the title and sends the target domain and the retrieved page title text to the console.log:

// Create the XHR object.
function createCORSRequest(method, url) {
  var xhr = new XMLHttpRequest();
  if ("withCredentials" in xhr) {
    // XHR for Chrome/Firefox/Opera/Safari and IE >= 10
    xhr.open(method, url, true);
  } else if (typeof XDomainRequest != "undefined") {
    // XDomainRequest for IE <= 9
    xhr = new XDomainRequest();
    xhr.open(method, url);
  } else {
    // CORS not supported
    xhr = null;
  }
  return xhr;
}

// utility function to parse the title tag from the response
function getTitle(text) {
  return text.match('<title>(.*)?</title>')[1];
}

// Make the actual CORS request
function makeCorsRequest() {
// we want the title of the page at targetdomain.com
  var url = 'targetdomain.com';
// use the GET method to return the entire page
  var xhr = createCORSRequest('GET', url);
  if (!xhr) {
    // log message if CORS is not supported
    console.log('CORS not supported');
    return;
  }
  // Response handlers.
  // on readyState = load
  xhr.onload = function() {
    // xhr.responseText contains the HTML for the page at targetdomain.com
    var text = xhr.responseText;
    // send the responseText to the utility function to extract the page title
    var title = getTitle(text);
    // do something with the processed responseText, in this case log a message
    console.log('response from request to ' + url + ': ' + title);
  };

  // error handler
  xhr.onerror = function() {
    console.log('error making the request');
  };

  // send the request
  xhr.send();
}

What happens in this CORS request?

  1. The XMLHttpRequest request is created with the detection of CORS and error handling.

  2. The responseText request returns the contents of the page at targetdomain.com with GET.

  3. The getTitle function is executed on the responseText request, and it returns the title text.

  4. The target domain URL and the title text are sent to the console.log.

You're probably thinking, "Big deal! I can get the title text in other ways.". But you could do more than retrieving a DOM element.

Distributing DOM elements to multiple domains

Let's consider a scenario in which you want to distribute a block-level DOM element, for example, a navigation menu from a target domain to multiple pages on multiple domains, along with customized CSS and JavaScript for the menu. You only change the navigation menu once on the target domain and copy it to multiple pages on multiple domains with CORS.

We will examine the pieces and then put them all together.

A script tag on the local domain embeds a script from the target domain. The same origin policy allows script tags to request resources across domains. The CORS script on the target domain will contain the createCORSRequest function and a request like this:

var request = createCORSRequest("GET", "targetdomain.com/header.php");

The CORS request allows you to GET a PHP file from the target domain and use it on the local domain.

You are not limited to requesting the HTML for a page on the target domain in the responseText request, as in example 2; header.php on the target domain may contain HTML, CSS, JavaScript, and any other code that is allowed in a PHP file.

Note

We are only reading header.php. Its contents are created by a process on the target domain outside of CORS. A script on the target domain scrapes the navigation menu and adds the necessary CSS and JavaScript. This script may be run as a Cron job to automatically update header.php, and it can also be triggered manually by an administrator on the target domain.

If the request is successful, it returns the contents of header.php and replaces the contents of a DOM element #global-header on the local domain with the responseText request:

if (xhr){
  xhr.onload = function(){ 
    // do stuff if request is successful;
  document.getElementById('#global-header').innerHTML =       xhr.responseText ;
  };
 request.send();
}

Adding the Access-Control-Allow-Origin header in header.php on the target domain allows access from the local domain. Since header.php is a PHP file, we add the header with the PHP code. Use the wildcard * to allow access from any domain because we making the CORS request from multiple domains:

<?php
header('Access-Control-Allow-Origin: *');
?>

You can place the CORS request script on the target domain as cors_script.js and trigger it with the script tag on your local domain. The responseText request is sent to any local domain page that contains the script tag. The DOM selector #global-header is replaced on the local domain with the responseText request contents of header.php, which contains the navigation menu HTML, CSS, and JavaScript from the target domain. We are also going to replace the logo image on the local domain with the one from the target domain.

By placing a script tag on your local domain page, you can access a target domain from any local domain, run a script on it, and do something on your local domain:

<script src="http://targetdomain.com/cors_script.js"></script>

Putting it all together

The contents of cors_script.js in the target domain are as follows:

// Create the XHR object.
function createCORSRequest(method, url) {
  var xhr = new XMLHttpRequest();
  if ("withCredentials" in xhr) {
    // XHR for Chrome/Firefox/Opera/Safari and IE >= 10
    xhr.open(method, url, true);
  } else if (typeof XDomainRequest != "undefined") {
    // XDomainRequest for IE <= 9
    xhr = new XDomainRequest();
    xhr.open(method, url);
  } else {
    // CORS not supported
    xhr = null;
  }
  return xhr;
}

// set some variable values for use in the request processing
// the Target Domain  is contained in document.domain
var rawdomain = document.domain;
// add the http:// scheme
var sourceURL = "//" + rawdomain; // use the Protocol-relative shorthand //

// define XHR request
var request = createCORSRequest("get", sourceURL + "/[path-to-file]/header.php");

// send the request and process it if it is successful
if (request){
  request.onload = function(){ 
    // do stuff if CORS request is successful and loads
    // if it fails, there is no replacement of existing HTML in target site
    // replace contents of #global-header on Local Domain with the responseText
    document.getElementById('global-header').innerHTML = request.responseText;
    // use logo image from Target Domain inside #branding container
 on Local Domain
    document.getElementById('branding').getElementsByTagName('img')[0].src = sourceURL + '[path-to-logo]/targetdomain_logo.png';
  };
  request.send();
}

The navigation menu is automatically distributed to any number of pages on any number of domains via CORS whenever a page with the script tag loads.

Securing when all domains are whitelisted

What happens if someone copies your script tag to some other domain where the script was not intended to run? Since we whitelisted access from any domain with Access-Control-Allow-Origin: *, the request will be allowed from any domain; if the page also has the matching DOM selector #global-header, the script will copy the content from the target domain to the page making the request.

Although the W3C specification for CORS recommends providing a list of allowed origins, in practice, this is not widely implemented in browsers.

Tip

Ways to add security when a CORS header whitelists all domains

Techniques have been proposed to first match $_SERVER['HTTP_ORIGIN'] to an allowed list, then write the header that allows the matched origin. Since $_SERVER['HTTP_ORIGIN'] is not reliable, or the requesting domain may be served via a CDN that does not match the expected domain, this technique may not work.

An alternative method is to add allowed domains in .htaccess or in the server conf, which may have the same trouble with CDN domains.

Methods to add security when a CORS header whitelists all domains

There are a few methods to secure when all the domains are whitelisted in the CORS header. The following code compares the HTTP_ORIGIN with a list of allowed domains; if it matches, then the CORS header is written using the matched domain:

$http_origin = $_SERVER['HTTP_ORIGIN'];
if ($http_origin == "http://domain1.com" || $http_origin == "http://domain2.com" || $http_origin == "http://domain3.info")
{  
    header("Access-Control-Allow-Origin: $http_origin");
}

This technique may not work because $_SERVER['HTTP_ORIGIN'] is not reliable, or the requesting domain may be served via a CDN that does not match the expected domain.

An alternative method is to add allowed domains in .htaccess or in the server conf, which may have the same trouble with CDN domains.

Note

CORS headers can not provide reliable security

The CORS headers give browser information about allowed domains, but some other security policies, such as cookies or OAuth, can enforce tighter security in your application.

Simple CORS request methods

Most CORS request methods use either GET or POST, and less often HEAD. Keep these differences in mind when you are selecting the method to use:

  • Browsers cache the result from a GET request; if the same GET request is made again, then the cached result will be returned. Repeating a GET request that has been cached will NOT return a response after the first request. If your code checks for a response, it will only be returned the first time.

  • The POST method is typically used when you are updating information on the server. Repeating a POST method more than once may not return the same result. A POST will always obtain the response from the server. The content is sent separately from the headers in POST, which makes it more complicated than a simple GET request.

  • The HEAD method is used to check resources, so only the headers are returned without any content. HEAD can check for the existence of a resource, its size, or to see whether it has been recently updated.

    Tip

    GET, POST, and HEAD function in CORS exactly as they do for an XMLHttpRequest request.

 

CORS with Preflight


Preflight is a request the XHR object makes to ensure it's allowed to make another request.

Note

The CORS specification requires browsers to preflight requests that do the following:

  • Use any methods in the request other than GET, POST, or HEAD.

  • Include custom headers

  • Include content-type other than text/plain, application/x-www-form-urlencoded, or multipart/form-data

There's no preflight by default in CORS. Adding preflight makes your application more robust and handles errors better. However, it can also introduce complexities, which may be unnecessary when you are confident that the XHR request you need to make will be answered, and you only need to use GET, POST, or HEAD.

Triggering a preflight by setting a custom header

To trigger a preflight, set custom headers on the XHR request; the Access-Control-Allow-Methods header determines which HTTP methods can be used.

The preflight request

The following PHP code verifies for the OPTIONS request method during preflight. The server responds with the X-Requested-With header permitted:

if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
  // return only headers
  // The Preflight checks that the GET request method is supported
  if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) && $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] == 'GET') {
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Headers: X-Requested-With');
  }
  exit;
}else{
  // error-handling code if the OPTIONS request method is unavailable
}

The preflight response

A successful server response returns the X-Requested-With method:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: X-Requested-With

CORS via jQuery

CORS via jQuery does not use preflight.

jQuery specifically avoids setting the custom header when making a CORS request. Therefore, it is better to use a separate preflight method when using jQuery for CORS.

Note

Here is the comment in the jQuery xhr.js library explaining why preflight is not used:

// X-Requested-With header

// For cross-domain requests, seeing as conditions for a preflight are

// akin to a jigsaw puzzle, we simply never set it to be sure.

// (it can always be set on a per-request basis or even using AJAXSetup)

// For same-domain requests, won't change header if already provided.

Known issues with CORS preflight

There are some common issues that developers face while implementing CORS preflight.

Preflight in Firefox

The CORS preflight request fails in Firefox when the OPTIONS request needs to be authenticated, causing the cross-origin request to fail. The request fails because authentication tokens are not sent with the preflight request. If the OPTIONS request fails, the preflight will result in 405 (method not allowed). Firefox ignores the request when the preflight fails.

Preflight in Chrome

Unlike Firefox, Chrome allows the request even if the option fails in preflight if the request and response headers are correct.

Preflight in Internet Explorer

Even when using withCredentials, IE doesn't send the auth tokens to preflight.

Tip

Should we avoid preflight entirely?

The best advice is to avoid using preflight entirely, unless you need to check whether requests are allowed.

Non-simple CORS request methods and headers require preflight

Any CORS request that uses a non-simple method or header requires preflight.

GET, POST, and HEAD are considered simple requests (and are case-sensitive). They do not require preflight.

The simple headers that do not require preflight are as follows:

  • Cache-control

  • Content-language

  • Content-type

  • Expires

  • Last-modified

  • Pragma

Any other method or header requires preflight.

Using the XMLHttpRequest level 2 event HandlersOriginally, XMLHttpRequest had only one event handler: onreadystatechange. XMLHttpRequest2 introduces new event handlers.

You may have noticed that when defining the XHR objects, we have used request.onload, which corresponds to the onload event when the request has successfully completed since we are interested in knowing whether the request has been successful.

Event handler

Description

Onreadystatechange

readyState property changes

onloadstart*

request starts

Onprogress

during loading and sending data.

onabort*

request has been aborted

Onerror

request has failed

Onload

request has successfully completed

ontimeout

specified timeout has expired before the request could complete

onloadend*

request has completed (success or failure)

* IE's XdomainRequest does not support handlers marked with asterisks

Checking for the withCredentials property

Check whether withCredentials property is available to determine whether the browser supports XMLHttpRequest level 2 event handlers. This could be handled as a preflight.

 

Troubleshooting and debugging CORS


Detecting problems with CORS requires enabling the crossorigin attribute in the <script> tag.

Normal script tags will pass the least information to window.onerror for scripts that do not pass the standard CORS checks. To allow error logging for sites that use a separate domain for static media, several browsers have enabled the crossorigin attribute for scripts using the same definition as the standard crossorigin attribute for the <img> tag.

Browser support for crossorigin attribute in the <script> tag

BrowserChromeFirefox Gecko)Internet ExplorerOperaSafari
Crossorigin attribute supportVersion >= 30.0Version >= 13Not supportedVersion >=12.50(Yes)
 

CORS with jQuery


jQuery's $.ajax() method can be used for standard XHR and CORS requests.

Note

Things to know about CORS with jQuery2

JQuery's CORS implementation doesn't support IE's XDomainRequest object, which is needed prior to Internet Explorer 10. There are jQuery plugins and workarounds. $.support.cors can signal support for CORS. It is set to true if the browser supports CORS (but in IE it always returns false). This can be a quick way to check for CORS support.

In jQuery, define the XHR functions using the same techniques as for CORS with JavaScript:

$.ajax({
  // The 'type' property sets the HTTP method
  // Any value other than GET, POST, HEAD (eg. PUT or DELETE methods) will initiate a preflight request
  type: 'GET',

  // The Target Domain URL to make the request to
  url: 'http://targetdomain.com',

  // The 'contentType' property sets the 'Content-Type' header
  // The JQuery default for this property is
  // 'application/x-www-form-urlencoded; charset=UTF-8'
  // If you set this value to anything other than
  // application/x-www-form-urlencoded, multipart/form-data, or text/plain,
  // you will trigger a preflight request
  contentType: 'text/plain',

    xhrFields: {
      // The 'xhrFields' property sets additional fields on the XMLHttpRequest
      // This can be used to set the 'withCredentials' property
      // Set the value to 'true' to pass cookies to the server
      // If this is enabled, your server must respond with the header
      // 'Access-Control-Allow-Credentials: true'
      // Remember that IE <= 9 does not support the 'withCredentials' property
      withCredentials: false
    },

    headers: {
      // Set custom headers
      // If you set any non-simple headers, your server response must include
      // the headers in the 'Access-Control-Allow-Headers' response header
    },

    success: function() {
      // Handler for a successful response, do something with the response.Text
    },

    error: function() {
      // Error handler
      // Note that if the error was due to an issue with CORS,
      // this function will still be triggered, but there won't be any additional information about the error.
    }
});

jQuery CORS AJAX plugin

A jQuery plugin for CORS is available at http://plugins.jquery.com/cors.

The plugin sends cross-domain AJAX requests through corsproxy.io.

Chapter 2, Creating Proxies for CORS, gives details about using proxies with CORS.

 

Enabling CORS globally with server configuration


Depending on your web server, you may be able to add the Access-Control-Allow-Origin header to allow CORS globally on every page in the server configuration.

Enabling CORS globally is not recommended since it removes granular control per page or application.

Tip

Enabling CORS globally may be useful when you are only running a single application on the server, and every page needs to support CORS. By adding the header in the server configuration, you do not need to add the Access-Control-Allow-Origin header on a per-page basis.

 

Alternatives to CORS


There are other ways to work around the same-origin policy. CORS provides better basic security, error handling, preflight, and other methods that make it a superior choice for cross-origin sharing compared to these alternatives

Alternative methods include the following:

  • JSON-P

  • WebSocket

  • window.postMessage

JSON-PJSONP (later dubbed JSON-P, or JSON-with-padding) was proposed in 2005 as a way to use the <script> tag to request data in the JSON format across domains.

The term "padding" refers to a callback function, which is defined as a query parameter attached to the <script> tag. The callback function is defined on the target domain. The <script> tag on the local domain loads a function or service on the target domain. When the script executes, the function on the target domain is called, and the data returned from the target domain is passed to the callback function on the local domain.

There is no official definition or specification for JSON-P.

Example of JSON-P

  1. A callback function is defined on the local domain:

    function handle_data(data) {
       // something is done to the data received from the Target Domain
    }
  2. A <script> tag on the local domain loads the script (http://targetdomain/web/service) from the target domain and passes the results to the callback function handle_data on the local domain:

    <script type="application/javascript" src="http://targetdomain/web/service?callback=handle_data" </script>

Using JSON-P – limitations and risks

  • JSON-P does not use AJAX XHR; therefore, error detection and handling are not possible until the data is passed to the callback function.

  • Trust in the target domain is implicit. If the target domain is compromised, the local domain becomes vulnerable as well. JSON-P is subject to cross-site request forgery (CSRF or XSRF) attacks because the <script> tag is not restricted by the same-origin policy. A script tag on a malicious page can request and obtain JSON data of another domain. If the user is authenticated at the endpoint domain, passwords or other sensitive data may get compromised.

  • Rosetta flash uses adobe flash player to exploit servers with a vulnerable JSON-P endpoint. It causes the Adobe Flash Player to accept a flash applet as originating from the same domain.

Proposed JSON-P validation standard

The standard would make JSON-P safer.

  • Limit the function ("padding") reference of the JSON-P response to a single expression as a function reference or an object property function reference. A single pair of enclosing parentheses should follow the expression, with a valid and parsable JSON object inside the parentheses.

    Examples of safer JSON-P functions are as follows:

    functionName({JSON});

    obj.functionName({JSON});

    obj["function-name"]({JSON});

  • Only whitespace or JavaScript comments may appear in the JSON-P response since whitespace and comments are ignored by the JavaScript parser. The MIME-type application/json-p and/or text/json-p must be included in the requesting <script> element. The browser can require that the response must match the MIME-type.

Note

However, MIME-type application/json-p and/or text/json-p are not supported by any browser. CORS is a safer and more robust method than JSON-P for sharing resources across domains with JavaScript.

WebSocket

WebSocket provides full-duplex communication channels over a single TCP connection. The WebSocket protocol was standardized by the IETF (https://tools.ietf.org/html/rfc6455) in 2011, and the WebSocket API is a candidate recommendation by the W3C (http://www.w3.org/TR/websockets/).

WebSocket uses TCP, not HTTP; nor does it use AJAX/XHR.

Socket.io provides a framework to use WebSocket by creating a node.js server for the socket (http://socket.io/).

WebSocket handshakes

The initial handshake over HTTP sets up the connection and communicates the origin policy information.

If the handshake is successful, the data transfer continues via TCP.

WebSocket creates a two-way communication channel, where each side can, independently from the other, send data at will.

WebSocket and cross-domain resource sharing

A cross-domain WebSocket is enabled with the domain (host) header to accept/deny requests.

Risks of using WebSocket for cross-domain resource sharing

  • The header can be spoofed. In order to secure the connection, you will need to authenticate the connection by other means.

  • The same-origin policy is not observed in WebSocket; therefore, Cross-Site WebSocket Hijacking (CSWSH) is possible.

  • WebSocket does not handle standard safeguards that need to be implemented outside of the WebSocket:

    • Authentication

    • Authorization

    • Sanitization of data

The window.postMessage method

The postMessage method is part of the W3c candidate recommendation for HTML5 Web Messaging ().

postMessage allows messages between discrete documents. The documents may include an iframe embedded in a document, or any other window objects.

The postMessage method dispatches MessageEvent in the target window when a script is completed.

postMessage risks and security measures

  • Any window can send a message to any other window. An unknown sender can send malicious messages. The sender's identity can be verified using the origin and source properties. If a site you trusted gets compromised, it can send cross-site scripting messages. The syntax of the received message can be verified against an expected pattern.

  • A malicious script can spoof the location property of the window and intercept data. Similar to avoiding the wildcard in the Access-Control-Allow-Origin header in CORS, specify an exact target.

Tip

The obvious security measure is not to use postMessage and not to add any event listeners for message events if you don't expect to receive messages.

 

Summary


We looked at the same-origin policy, which limits cross-origin resource sharing. We covered a lot of the basics needed to work around the same-origin policy with CORS, including the header and request.

We saw how a script tag on a local domain can retrieve resources from a target domain as responseText request and how we can then do things with the responseText request on the local domain.

We have learned when preflight is helpful, and when it is required.

We have learned how to enable the crossorigin attribute in the script tag for troubleshooting.

We have looked at CORS with jQuery and its limitations.

We have compared CORS with other cross-origin methods: JSON-P, WebSockets, and window.postMessage. We have learned why CORS can be better and more secure than these methods.

In the next chapter, we will learn how to use proxies for CORS, for example, using the CORS plugin for jQuery with corsproxy.io.

About the Authors
  • Randall Goya

    Randall Goya has been a Senior Web Developer and Application Architect for enterprise organizations for several years, mostly specializing as a Drupal Consultant. Drupal as a framework is integrated with so many other applications and APIs, including payment gateways, media (Brightcove, YouTube, mp3, and video players), messaging (Amazon SQS, Mulesoft), as a content repository for other frameworks (WordPress), and for native mobile applications, and VOIP.

    Browse publications by this author
  • Rajesh Gunasundaram

    Rajesh Gunasundaram is a software architect, technical writer and blogger. He has over 15 years of experience in the IT industry, with more than 12 years using Microsoft .NET, 2 years of BizTalk Server and a year of iOS application development. Rajesh is a founder and editor of technical blogs programmerguide and ioscorner and you can find many of his technical writings on .Net and iOS. He is also the founder and developer of VideoLens, a platform that analyses videos uploaded in Facebook pages and YouTube channels. Rajesh has also written four other books for Packt publishing. Rajesh worked on client premises located at various countries such as UK, Belarus and Norway. He also has experience in developing mobile applications for iPhone and iPad. His technical strengths include Azure, Xamarin, ASP.NET MVC, Web API, WCF, .Net Framework / .Net Core, C#, Objective-C, Angular, Bot Framework, BizTalk, SQL Server, REST, SOA, Design Patterns and Software Architecture. Rajesh is an early adopter of Angular since AngularJS. He has developed Rich interfaces using Angular, Bootstrap, HTML5 and CSS3. He has good experience in translation of designer mock-ups and wireframes into an AngularJS front-end. Good at unit testing Angular applications with Karma. Expertise in handling RESTful services in Angular. Supporting various web products developed using AngularJS and Angular.

    Browse publications by this author
CORS Essentials
Unlock this book and the full library FREE for 7 days
Start now