Thursday, August 28, 2014

Cross-Origin Resource Sharing

Cross posting an article written by me last year for a technical magazine but was not published.
--
Web is growing fast with lots of new services being added everyday. Advancements in technologies like HTML5, JS, and CSS3 make the web so powerful that we can almost do everything on the web today. With the increase in the number of services, the need for cross referencing the services also increases. A web application providing photo editing service may want to use the service of photo sharing sites like Instagram or Flickr. Almost all the service providers expose APIs for others to consume their services. While consuming the service from a web application back-end is easier, it is not so easy using the client side technologies. Browsers impose lots of restrictions in accessing resources originating from domains other than the current domain. 



Same-origin Policy
Same origin policy is a security policy on browsers which enforces restrictions to the resources originating from domains other than the domain of the current page. The resource can either be static or dynamic. If the resource from a cross domain is requested dynamically using the XMLHttpRequest (AJAX request), the browser will kill the request with a 401 response. If the resource (JS script) is loaded statically (using script tag), the loaded script will not be able to access the DOM contents of the current page.

There are various techniques employed on the client side to allow access to cross origin resources. In the flash world, traditionally, the Cross domain policy descriptor file (crossdomain.xml) has been used to allow access to cross origin resources. In JavaScript, techniques like JSONP and custom proxy have been used in the past. Both of these techniques have inherent problems with them - error handling is a nightmare with JSONP, the maintenance and house keeping of the custom proxy is painful. Cross-Origin Resource Sharing a.k.a CORS is an elegant way of providing access to cross origin resources.   

Note: Domain name includes the scheme, host name and the port number. Accessing resources on http://example.com from https://example.com is considered cross origin access. 

What is CORS?
Cross-Origin Resource Sharing (CORS) is a W3C spec that allows access to resources from cross domains. CORS defines a set of headers and rules using which the client (browser) and the server can interact to determine whether or not to allow the cross-origin request. There are two parties involved in a CORS request - a client and a server. Most of the modern day browsers take care of the client part of the CORS request.  One can easily configure the server to support CORS or use one of the libraries already out there for the language of your choice. As a developer, one needs to explicitly inform the browser to make CORS request. 

There is a misconception that CORS is meant only for dynamic contents requested via XMLHttpRequest. But, it is very much applicable for static resources also (images and scripts). People often confuse security of the resource with CORS access. CORS has got nothing to do with the security of the resources being accessed. The server stack should have its own authentication and authorization layers if needed. CORS is not an authorization technique.

Browser Support:
Almost all the modern day browsers support CORS. 

CORS for dynamic contents
When a script attempts a cross-origin request, the browser will initiate a pre-flight request to the server. In the pre-flight request, browser will automatically include one or more CORS request headers, depending on how the actual cross-origin request was initiated. Then, the server responds back with the pre-flight response message by setting appropriate response headers. Browser uses the response headers to determine if the cross-origin request has to be allowed or not. 

Things to note regarding a Pre-flight request:
  • Pre-flight request is internal to the browser and you may not find any traces of it in the network panel of the developer tools. You will be able to trace the request using any of the network sniffing tools. 
  • Pre-flight request uses the HTTP verb 'OPTIONS'
  • Browser can choose to skip the pre-flight if the actual cross-origin request is a simple GET / POST request with basic HTTP headers.
  • It is advisable to have the pre-flight request unauthenticated. (As the browsers don’t send auth tokens for pre-flight request)

CORS Request Headers:
  • Origin: The Origin header indicates where the cross-origin request or pre-flight request originates from. 
  • Access-Control-Request-Method: The Access-Control-Request-Method header indicates which method will be used in the actual request. Browser will set this header automatically when the HTTP method of the actual cross-request is anything other than GET, POST or HEAD. 
  • Access-Control-Request-Headers: The Access-Control-Request-Headers header indicates which headers will be used in the actual request. 

CORS Response Headers:
  • Access-Control-Allow-Origin: The Access-Control-Allow-Origin header indicates whether a resource can be shared based by returning the value of the Origin request header, "*", or "null" in the response.
  • Access-Control-Allow-Credentials: When part of the response to a pre-flight request Access-Control-Allow-Credentials header indicates that the actual request can include user credentials.
  • Access-Control-Expose-Headers: The Access-Control-Expose-Headers header indicates which headers are safe to expose to the API of a CORS API specification.
  • Access-Control-Max-Age: The Access-Control-Max-Age header indicates how long the results of a pre-flight request can be cached in a pre-flight result cache.
  • Access-Control-Allow-Methods: The Access-Control-Allow-Methods header indicates, as part of the response to a pre-flight request, which methods can be used during the actual request.
  • Access-Control-Allow-Headers: The Access-Control-Allow-Headers header indicates, as part of the response to a pre-flight request, which header field names can be used during the actual request.
CORS Request Flow. Image courtesy: HTML5Rocks.com
How to enable CORS on the server side?
Adding support for CORS on the server side is not as trivial as it is on the client side. If you have to build the support yourself, the first thing you need to do is to create a route for OPTIONS to handle pre-flight requests. Then, the server must parse the CORS request headers and set the response headers based on the origin and the resource requested for. 

There are well written third party libraries available to ease up this task for us. For Java, you can use the CORS filter written by Vladimir Dzhuvinov. CORS-Python for Python. CORS Express middleware for Node.js.

More details on how to enable CORS on your server side can be found here.

How to enable CORS on the client side?
To initiate a CORS request, you need to use Cross Domain XMLHttpRequest (XMLHttpRequest-2) or Internet Explorer’s XDomainRequest. Standard AJAX requests do not send or set any cookies by default. In order to include cookies as part of the request, you need to set the XMLHttpRequest’s .withCredentials property to true. Making a CORS request is as simple as a regular XMLHtttpRequest. Most of the heavy lifting on the client side is done by the browser itself. 

JQuery’s $.ajax() supports both regular XHR and CORS requests. It supports the withCredentials property as well. However, it doesn’t use XDomainRequest in Internet Explorer. You may want to try out Nicholas Zakas’ helper method as well.

CORS for Images
Though CORS is most widely used in the context for AJAX requests, it is important to be aware of its use cases with loading images from cross domain. Whenever an image is loaded from another domain (using img src attribute), the browser will happily load the image. However, when you try to manipulate the image by loading it on to a canvas, the user agent will throw security exceptions for the APIs getImageData and toDataURL. To manipulate the image and provide an option to export the image, you need to request the image with CORS. You can do that with the help of crossOrigin attribute. The default value of the attribute is anonymous. If the source image is authenticated, you can pass the value as use-credentials.

<img src="http://example.com/profile.png" crossOrigin>

CORS for Scripts
Like with images, loading JavaScript using crossOrigin attribute has its advantages. A script loaded with CORS support will be able to make DOM manipulations on the current site. Another use case here is error handling of scripts from cross-domain. By default, most modern browsers do not pass any information about errors to window.onerror if the script is loaded from across domains. With CORS, it is possible to track errors thrown from x-domain scripts. 

<script src="http://example.com/script.js" crossOrigin></script>

Known Issues
CORS is still a work in progress. Browser vendors continue to improve the CORS support offered by them. Here are some of the known issues with the browsers: (as of this article date)
  • In Firefox, the CORS pre-flight request fails when the server expects the OPTIONS request to be authenticated. Subsequently failing the actual cross-origin request. Chrome ignores the 401 on the OPTIONS and proceeds as expected to the original request. Remember, the auth tokens are not sent along with the pre-flight request and hence the request is failing with 401. (Firefox#778548, Chrome#139566)
  • If the server doesn't handle the OPTIONS request, Firefox will halt the actual request as the pre-flight will result in 405 (Method not allowed). However, Chrome allows the actual request if the request and response headers are set properly on the actual request. Firefox follows the CORS spec correctly and cancels the request once the pre-flight fails.
  • IE doesn't send the auth cookies to pre-flight request even when withCredentials property is set. (IE#759587)

CORS plays an important role in moving the web forward. In an ideal world, every web application should offer its services in an easily consumable way and should talk to each other as if the service was part of its own. Applications should provide their users the control to choose the service he / she prefers. CORS is the way to go to achieve all of that. Happy cross-origin scripting. 

-- Varun

No comments:

Post a Comment

google-site-verification: googlea4d68ed16ed2eea6.html