Faux Single Use URLs with ColdFusion and Amazon S3

Every now and then I come up with some code that while very simple, solves a problem quickly and easily for me. Recently we were asked to secure some files behind CF to control who can download them. This is simple enough, usually we would just store the files off of the webroot and use cfcontent to make a download for the logged in user.

However, these files were 1.5GB+. Obviously a web server wouldn't be the ideal place to host these files. And to be perfectly honest, when files are this large I prefer to push them out to the cloud. As most probably know, the Amazon S3 API has way to offer expiring URLs. Coldfusion 9.0.1 doesn't support this out of the box but Barney Boisvert's Amazon S3 CFC does thankfully. But really, this doesn't totally solve my problem. If I leave the link available for 5 minutes, the logged in user can give the URL to many other people who can start their download before the URL expires. On top of that, if I write the link to a page, the legitimate user might have tabbed away and by the time they click the link it could have expired.

So, my solution to this was to create a process in which the user could get their download, while expiring the URL almost immediately. So here is a simplified version of how I do this.

First of all, I am going to create download.cfm, which the only job it has, is to return an S3 URL that will expire in 2 seconds.

All this page does is take your AWS Key and AWS Secret and create an instance of the amazonS3 cfc. Once you have your s3 object you can use the s3URL method to pass in your bucket, objectkey (which basically means it's location on s3 minus the bucket location), the request type which I just set to regular and the lifetime of the url in seconds. So here this URL will last only 2 seconds.

So okay, we have a page that can create an s3 URL that lasts only 2 seconds...what good does that do for us?

Basically what is going on here is we have a link that doesn't actually go anywhere. We store our S3 Object key in the data-file attribute of the link (though you could use another attribute, like title or alt if you prefer).

Now on jQuery, we want to intercept the clicks on this link and make an ajax call to our download.cfm page, passing our objectKey as the file. As expected download.cfm will return a link that expires quickly. As soon as that URL is returned by download.cfm, we call a download function that creates an iframe and loads that URL. When the iframe is finished loading the download prompt will pop up and at the same time the iframe is removed by jQuery.

This is all essentially transparent to the user other than if they browse the ajax request made by their browser. Even if they do inspect the response and try the S3 link we returned, it will be expired, making this method produce a virtually "one time use" Amazon s3 link.

I just slap this code behind normal CF security and it prevents unauthorized downloads of files.

A quick note, 2 seconds might be too short if the connection to s3 isn't very fast. If you are concerned the iframe cannot load the s3 URL sub 2 seconds after the URL is generated you could safely bump the URL timeout to 5 seconds or so.

There may be a better/faster/simpler way to do this in CF or Java that I could implement, however, I haven't been able to find it yet. If you know of one I'd be glad to hear about it. Otherwise hopefully this is helpful to someone.