Orange is my favorite color

I have been working with a contractor in India recently on a Javascript project using Ext. I wanted to outsource the front-end development since we have extensive APIs that would take an outside developer longer to get up to speed on. Due to PCI DSS and general security practices however, we can’t just let the contractor log in and push code or make changes to our development server. In fact, we didn’t even give him Subversion access meaning he would have to code against our data services remotely.

The Setup

Development server at dev.domain.com. Off-site developer building an application on his laptop and connecting his AJAX/xmlHttpRequest calls to dev.domain.com.

The Problem

Browsers restrict cross-domain requests for security purposes. So Javascript at http://localhost can’t retrieve data from a remote API like http://dev.domain.com. In Firefox + Firebug, you would see the following:

Error: uncaught exception: Permission denied to call method XMLHttpRequest.open

There is a hack for Firefox but that doesn’t help you with IE, Safari or Chrome. We started out this way but when it came time to deploy we found issues with Safari and Chrome.

These were things we could have detected much earlier in the development cycle had the Javascript developer been able to test in those browsers. It was a big mistake on our part to delay the cross-browser testing and it’s a testament to the stability of the Ext components that we didn’t have 10,000 other issues.

The Solution

If you’re developing with Apache, the answer is quite simple actually: use a reverse proxy. Apache’s mod_proxy will take a request for something like “/foo” and actually tunnel the request to some remote destination like “http://dev.domain.com/bar”. The end result is that your web browser thinks you’ve made a call to http://localhost/foo but in reality you’re sending and retrieving data from a remote server. Security implications solved!

I searched for a long time but never found an intersection between AJAX development, cross-domain xmlHttpRequest restrictions and Apache’s mod_proxy. It wasn’t until I thought, “hey, that might work” and started searching specifically for reverse proxy details did I turn up an example.

Apache Configuration

This is a pretty basic Apache feature – first you will need to load the required modules:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule rewrite_module modules/mod_rewrite.so

Let’s assume that I want to access a file at http://dev.domain.com/remote/api.php. You would put all of the following into a <VirtualHost>:

# start mod_rewrite
RewriteEngine On

ProxyRequests Off
<Proxy>
	Order deny,allow
	Allow from all
</Proxy>

ProxyPass /apitest/ http://dev.domain.com/remote/api/
ProxyPassReverse /apitest/ http://dev.domain.com/remote/api/
RewriteRule ^/apitest/(.*)$ /remote/api/$1 [R]

Restart Apache and make a browser request to http://localhost/apitest/api.php and you should receive a response from the remote server at http://dev.domain.com/remote/api/api.php. The RewriteRule will pass along any query-string parameters too. Done!

If you use IIS instead of Apache, you should be able to do something similar with ISAPI_REWRITE to accomplish the same functionality. This is also applicable to other technologies like Adobe Flex or Flash which also have cross-domain restrictions.

The advantage over other solutions like a vanilla proxy server or server-side script that translates the request for you is that it requires few, if any, changes to your code. It’s also payload-agnostic so it will work with any number of remote services without care for the request or response format.

There’s one final tweak we could make so our development environment mirrors our staging and production servers. If the final endpoint for the Javascript will be:

http://dev.domain.com/service/foo?var=value

Then we could use the same relative URL “/service/foo?var=value” and make our reverse proxy mirror that structure:

ProxyPass /service/ http://dev.domain.com/service/
ProxyPassReverse /service/ http://dev.domain.com/service/ 

Now there is no change necessary in the Javascript; just make your request to “/service/foo?var=value” on either localhost or dev.domain.com and the browser will be properly routed to the right destination without running into any security restrictions!

1 Comment

  1. Joran Greef said:

    on March 3, 2009 at 7:08 am

    Thanks for your post. I’ve been through the same areas as you in this regard. I’ve also tried the Apache proxy approach. The problem is it’s harder for clients hosting with IIS or shared servers to try this. It also requires more work for your client to interface with your API. The worst, however, is that your latency is effectively doubled, adding on up to 2 seconds per request. In addition, your clients become partner to bandwidth expenses relating to their use of your API.

    There is an excellent solution discovered by Kris Zyp as recent as July 2008 however: http://www.sitepen.com/blog/2008/07/22/windowname-transport/ I did a quick port of it and have been using it extensively for the past couple of months. It’s cross-browser and requires only a small touch of work to your server’s api to enable it to respond to cross-domain requests. It doesn’t have the same request size limitations as the dynamic script tag hack, and it doesn’t have the unbelievable complexity of Google’s nested iframe approach which requires chunking to overcome response size limits.

{ RSS feed for comments on this post}