Dynamically controlling cross-domain permissions
What are crossdomain.xml files?
As any developer who has ever dipped their toes into the murky pond that is cross-domain configuration with Flash will tell you, it is quite a confusing and somewhat aggravating task to accomplish. In this post, I’m going to demonstrate how you can control access to your APIs from Flash applications by dynamically generating crossdomain.xml files that work the same as static crossdomain.xml files.
But before we go any further, what the hell is a crossdomain.xml file?
The definition of a cross-domain configuration file is (taken from http://www.senocular.com/pub/adobe/crossdomain/policyfiles.html):
A cross-domain policy file is a XML document that grants a web client, such as Adobe Flash Player, permission to handle data across multiple domains. When a client hosts a content from a particular source domain and that content makes requests directed towards a domain other than its own, the remote domain would need to host a cross-domain policy file that would grant access to the source domain allowing the client to continue with the transaction. Policy files grant read access to data as well as permit a client to include custom headers in cross-domain requests.
Essentially what this means is that if you have a Flash application and you need to pull data (other than images or video) from a domain which is not the domain of your hosted application, you will need to place a crossdomain.xml file on the server you are requesting data from.
The configuration file needs to be placed in the webroot of the server; you can have a look at some of the more popular sites’ crossdomain.xml files:
- http://youtube.com/crossdomain.xml
- http://twitter.com/crossdomain.xml
- http://facebook.com/crossdomain.xml
This post is not a “how-to” on crossdomain.xml files unfortunately: for that, you can visit http://www.adobe.com/devnet/flashplayer/articles/cross_domain_policy.html – a great article by Lucas Adamski.
The Problem
You would like to control access to your application’s API based on a database table, for instance:
You have an application that serves up a list of products like Amazon.com to be made available via AMF requests (could be a REST-based architecture, SOAP, etc). You have a service offering whereby Flash/Flex application developers can register with your company to be allowed to access this wonderful API. You charge a set price per year for your service and you keep a database of authorized subscribers. At this stage, you can either allow any domain to access your API by using the following crossdomain.xml file:
1 2 3 4 5 6 | <!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <site-control permitted-cross-domain-policies="master-only"/> <allow-access-from domain="*"/> </cross-domain-policy> |
The problem with this configuration is that any domain can send requests to your API from their Flash applications, and for obvious reasons this is not such a great idea.
The other (more secure) option would be to manually stick in each subscriber’s domain name into the crossdomain.xml file as follows:
1 2 3 4 5 6 7 8 | <!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <site-control permitted-cross-domain-policies="master-only"/> <allow-access-from domain="example1.com"/> <allow-access-from domain="example2.com"/> <allow-access-from domain="example3.com"/> </cross-domain-policy> |
With the above configuration, you will start running into major headaches your subscribers either start cancelling their accounts or fail to pay in full. You will manually have to go and remove their domains from the configuration each time.
The Solution
When Flash applies a security policy like this, it automatically looks for a crossdomain.xml file on the requested server in the webroot. We can use a nifty combination of Apache rewriting and a PHP script to dynamically generate a list of authorized domains.
Apache Configuration
We want Apache to listen for an incoming request for our crossdomain.xml file, but instead of serving the XML file, we want to process a PHP script called generate-config.php to generate our configuration options. This example is quite simplistic, and I encourage you to take this further with whatever rules your application may require.
We are going to create a .htaccess file in the webroot of our server with the following contents:
RewriteEngine On
RewriteCond %{REQUEST_URI} crossdomain.xml
RewriteRule ^crossdomain\.xml$ generate-config.php [L]
This will present the output of our generate-config.php file as the contents of the crossdomain.xml file – essentially using another file’s content in place of another.
PHP & MySQL
In our generate-config.php file, we are going to:
- Connect to a database
- Run a SQL query that will return a list of subscribers
- Iterate through our list and add them to our XML output
- Echo the XML output back to Apache
First, create a database called crossdomain_test and then run the following SQL query to create our subscribers table:
CREATE TABLE IF NOT EXISTS `subscribers` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `domain` varchar(255) NOT NULL, `active` tinyint(1) NOT NULL DEFAULT '1', PRIMARY KEY (`ID`) ) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
Insert a couple of test rows:
INSERT INTO `subscribers` (`ID`, `name`, `domain`, `active`) VALUES (1, 'YouTube', 'youtube.com', 1), (2, 'Twitter', 'twitter.com', 1);
Now we’re going to create our generate-config.php file in the webroot:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?php // create our config skeleton according to the crossdomain.xml rules $xml = '<?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <site-control permitted-cross-domain-policies="master-only"/> </cross-domain-policy>'; // create $config variable to hold our config data $config = new SimpleXMLElement($xml); // connect to MySQL and select database $conn = mysql_connect("localhost", "username", "password"); mysql_select_db("crossdomain_test", $conn); // loop through all rows, and create xml nodes $res = mysql_query("SELECT domain FROM subscribers WHERE active = 1"); while($subscriber = mysql_fetch_object($res)) { $child = $config->addChild("allow-access-from", null); $child->addAttribute("domain", $subscriber->domain); } // echo back to Apache echo $config->asXML(); ?> |
If we navigate to the crossdomain.xml file in the browser, we should see:
1 2 3 4 5 6 7 | <?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <site-control permitted-cross-domain-policies="master-only"/> <allow-access-from domain="youtube.com"/> <allow-access-from domain="twitter.com"/> </cross-domain-policy> |
and if we delete one of the records out of the table (try deleting YouTube), and navigate to that page again, you’ll see:
1 2 3 4 5 6 | <?xml version="1.0"?> <!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd"> <cross-domain-policy> <site-control permitted-cross-domain-policies="master-only"/> <allow-access-from domain="twitter.com"/> </cross-domain-policy> |
And that’s it! Not too difficult at all
A Workaround for all frustrated developers consuming REST services
As a side-note, if you ever encounter a site that you need to pull data from and they do not have a crossdomain.xml file, all you have to do is write a simple “proxy” file with PHP (or whichever language you choose) that will accept one parameter (the URL to be called) and send the results back:
1 2 3 4 5 | <?php $url = $_GET["url"]; echo file_get_contents($url); ?> |
Make sure that this proxy file is sitting in your domain though, or a domain with a crossdomain.xml file present.
| Print article | This entry was posted by Danny Kopping on March 10, 2010 at 1:12 am, and is filed under Flash, Flex, Security. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |
about 2 months ago
Awesome post, really eye-opening. Can’t wait to try out this forgery!
I am struggling to find a way to load an swf file with library items of Sprites from my clients domain into an AIR application running on a mobile device; but for now i even can’t decide if it’s a permission problem (which would require Your tip/or not) or matter of sandbox/cross-scripting limitations (that can or not be overcome)..
Have a nice day!
about 2 months ago
Hey Daniel
My guess would be that you shouldn’t have many problems loading in a SWF into an AIR app because AIR runs on a different sandbox to the browser – much more open. You may want to look into ApplicationDomain though – that might be a thorn in your side when trying to do the kind of thing you’re attempting.