Software Alchemist

I am constantly seeking answers and ways of transforming desires into reality by coding

Cross Domain Javascript, Lessons Learned

| Comments

Since the time I’ve started at Twilio, I’ve been tasked with improving a web user interface of one of the internal services.The service consists of a REST API that is used by the web UI and a number of other clients. To better separate concerns, I’ve decided to build the UI as an HTML5 application communicating directly to the REST API and since I wanted to develop locally, without having to run my version of the API, I decided to enhance it a bit to make it Cross-Origin Resource Sharing specification (CORS) complaint. This post is my practical overview of CORS.

note The techniques described in this blog post won’t work with older browsers. All versions Opera browser specifically don’t support CORS.

Simple CORS communication

Let’s assume that our web UI is running on localhost and our API is served from api.example.com.

When browser sends request to a different origin, it adds the Origin header like so:

GET / HTTP/1.1
Host: api.example.com
Accept: text/html
User-Agent: Mozilla/5.0 (Macintosh)
Origin: localhost

The server then needs to add Access-Control-Allow-Origin header so the response might look like:

HTTP/1.1 200 OK
Date: Sat, 02 Apr 2011 21:05:05 GMT
Content-Type: text/html
Access-Control-Allow-Origin: localhost

<html>
  <!-- HTML -->
</html>

The fact that the value for the Access-Control-Allow-Origin header of the response matches value the browser sent in Origin header as part of the request tells it that it is safe to display the content.

With me so far?

Non-simple request methods with CORS

This communication is quite simple and easy to follow. Things get a little more complicated once we want to send requests using non-simple method - anything other than POST or GET. Let’s assume that we want to execute PUT /posts/1.

First, the browser sends a so-called “pre-flight” request to the API using OPTIONS HTTP method:

OPTIONS /posts/1 HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0 (Macintosh)
Origin: localhost

In order for the communication to continue, the server, in addition to already mentioned Access-Control-Allow-Origin, needs to respond with Access-Control-Allow-Methods header, which is an equivalent of the regular Allow header. Here is how it might look:

HTTP/1.1 200 OK
Date: Sat, 02 Apr 2011 21:05:05 GMT
Allow: GET, PUT, DELETE
Access-Control-Allow-Origin: localhost
Access-Control-Allow-Methods: GET, PUT, DELETE

note Server doesn’t allow POSTing to our imaginary post instance resource.

After that, only if the original request method it listed in the Access-Control-Allow-Methods header of the response will the browser continue to execute the original request:

PUT /posts/1 HTTP/1.1
Host: api.example.com
Accept: text/html
User-Agent: Mozilla/5.0 (Macintosh)
Origin: localhost

title=First%20Post&author=Bulat&body=Hello%20World!

Finally, the server responds with:

HTTP/1.1 200 OK
Date: Sat, 02 Apr 2011 21:05:05 GMT
Content-Type: text/html
Access-Control-Allow-Origin: localhost

<html>
  <!-- HTML -->
</html>

note Server can use the Access-Control-Max-Age header to tell the browser to send non-simple method requests without pre-flight request for a certain number of seconds.

Everything is still fairly straightforward and consistent across all browsers until this point.

CORS with Basic Auth

Now another important point is that the API of the service my web application is going to talk is enforcing HTTP Basic Auth. The communication workflow changes a little bit in that case.

As usual, browser requests a resource on a different domain:

GET / HTTP/1.1
Host: api.example.com
Accept: text/html
User-Agent: Mozilla/5.0 (Macintosh)
Origin: localhost

Server responds with already discussed Access-Control-Allow-Origin, additionally server sends Access-Control-Allow-Credentials header and Basic Authentication requirement:

HTTP/1.1 401 Authorization Required
Date: Sat, 02 Apr 2011 21:05:05 GMT
Content-Type: text/html
Access-Control-Allow-Origin: localhost
Access-Control-Allow-Credentials: true
WWW-Authenticate: Basic realm="Secure Area"

<html>
  <!-- HTML -->
</html>

note Please make sure to set withCredentials flag to true when sending ajax request. Here is how you would do it with jQuery:

1
2
3
4
5
6
$.ajax({
    url: 'http://api.example.com/'
  , xhrFields: {
      withCredentials: true
    }
});

Now if you are using a modern version of firefox, you should be prompted with standard Basic Authentication popup in your main window.

Once you’ve submitted the credentials and they’ve been verified by the server, communication continues as usual.

CORS Basic Auth gotchas

Using Basic Authentication with CORS specification means that you need to prompt authentication popup sent by the server to users of your application.

This works as is in Firefox, however Safari won’t show Basic Auth popup from different domain as a result of XMLHttpRequest. You can get around that by inserting a hidden <iframe/> element linking to the protected url. This will trigger the authentication popup and once the user has authenticated, you can execute direct XMLHttpRequests as usual.

Chrome prevents basic authentication from a different origin as a phishing attack period. The only way to get around that is to redirect your user to the target url and ask them to come back once authenticated. After the authentication is complete, communication can continue normally.

Conclusion

CORS specification is incredibly valuable when designing HTML5 applications that can talk to well defined HTTP APIs.

Cheers!

Useful reading

Comments