Protect your assets with expiring URLs

Please note: This post was written 5 years ago, which is an age in programming terms, so please don't treat this as gospel! I'll be updating all posts soon enough, but if you'd especially like this one to be updated let me know.

Keep it Secure!

Note: This article has been marked for a quality review and will soon be updated.

A problem that many web developers face is how to protect their assets. Specifically ones that users are supposed to pay for - say a video training site for instance, you don't want every Tom, Dick and Harry being able to guess the URL, or worse crack open the source code to find an unprotected URL. IF this happens companies can lose a lot of money, so it's kind of an important subject. The more astute of you out there (or those that read the title), might have already guessed that today we will be building a system that will allow us to force expiring URLs. Head over to the example to see what we'll be creating here.

To do this we won't be naming our files anything too funky, nor will we leave polite messages in our source; instead we will create a media-serving script that will make sure the request isn't coming from an unscrupulous source. For that we will need to get time on our side, and generate unique hash-keys that will allow us to verify the request. On a side note you could have a system which connected to a database to see if a hash existed, and if it wasn't expired, but for this tutorial, we will stick with using good ol' time. We'll only request our protected files from our script, and send 2 parameters to make sure we get what we want, they will be our file path, and our token in hash form. We will also use some nifty htaccess trickery to ensure users don't just copy and past the file path from our URL to their browser window - because that would just invalidate what we are about to do. Let's get started!

To begin we will create our media serving script, so go ahead and create a new file called asset.php - this script will decide if the request is genuine and if so will serve up our media. Here is the code for that file:

$t = $_GET['t'];
$url = $_GET['url'];

if(!empty($t) && !empty($url)){
	if($t==md5(date('his').'my_amazing_salt')){
		$image = imagecreatefromjpeg($url);
		header('Content-Type: image/jpeg');
		imagejpeg($image);	
	}else {
		echo 'Invalid URL!';
	}
}else {
	echo 'Invalid URL!';
}

Now the code above works under the pretence that we are only serving jpegs - if you wanted to only serve something like pngs for instance, you would just need to alter the content type, and fiddle with the loading of the actual file. So what does it actually do? Well at the top we make sure the variables passed to the script in the URL aren't empty, and then go on to validate them. The token system we're using here is one that generates a hash-key based on the current time, along with our own 'my_amazing_salt' string (just to make things even more secure) - which is then encrypted using the handy md5() function. We then load in the image, set the header (so it displays as an image and not a bunch of random characters), and serve up our media. If any of the checks fail along the way we just output a little message to our users.

If you want to have links that expire after an minute for example, you would simply alter the parameter passed to the date() function to something like "hi" for hour and minutes - check out the date function for more info.

Great, we've done the hard part, now we just need to replace any image URLs on our site with ones that point to our script that we just made. We can generate the URLs like so:

$url = 'asset.php?url=food.jpg&t='.md5(date('his').'my_amazing_salt'); 

And just alter the 'url' parameter where appropriate. I would recommend picking a totally random salt to add to the end of your encryption just so others won't have a clue what it is.

Ok, so now we have a working system whereby we can request our media from one file, and if that URL is visited by the user after the page load, they will be shown the door. But we still have one problem. Users will still be able to access our files using the path we pass to our script - they could just copy and paste it into their browser, and hey presto they've gotten what they wanted. So how do we fix this? Well the best way is using some htaccess magic to prevent direct loading of our media files.

Whack the following line of code into your htaccess file (if it doesn't exist, create one in your media directory):

RewriteEngine On
RewriteRule ^(.*).jpg$ asset.php?src=$1 [L]

That rule above simply sends all requests for images with the extension 'jpg' to the script we just created; and as we know, because the redirect will go to a URL without a token, it will invariably be rejected. At this point you might want to alter the code above to reflect whatever kind of media you are trying to protect.

And that's how to protect your assets! This method is useful when you want to conserve bandwidth and stop users from simply grabbing a load of subscription/payment-based files from your server.

Update

Is would seem the kids these days are into also protecting these moving-picture thingy-ma-jigs, so I thought I'd update this post to encompass protecting videos. Depending on what video format you wish to use, some of the code below will need changing, but I'll highlight that when we get there.

The code is nearly identical to what we used for images, with a couple of alterations, so here's the code you put in your .htaccess file:

RewriteEngine On

RewriteRule ^(.*).m4v$ asset.php?src=$1 [L]

Notice the ".m4v" extension - this is something you'll have to alter if you're using another format.

Then our PHP file changes to:

$t = $_GET['t'];
$url = $_GET['url'];

if(!empty($t) && !empty($url)){
	if($t==md5(date('his').'my_amazing_salt')){
		$filename = $_GET['url'];
		$fsize = filesize($filename);
		header('Content-Disposition: filename='.$url);
		header('Content-Type: video/quicktime');
		header('Content-Length: '.(string)$fsize);
		readfile($filename);
	}else {
		echo 'Invalid URL';
	}
}else {
	echo 'Invalid URL';
}
	

Again, notice the file extension here and alter accordingly. To include the video on your page you can do all manner of things, for instance you might want to use a flash player or the swanky <video> tag. In the video example I've used an iFrame.

And that's how to protect videos!

***

If you found this particularly useful and want to share the ♥ you can donate here.

***

4 Responses

  1. TommyLee

    great article. thank you.
    a small question
    how would you do this for a video file? I tried but since no "imagecreatefromjpeg" or "imagejpeg" etc exist for video I could not figure out how to do it 🙁

    Reply
    • Tom

      I'd properly look into using URLs with complex hashes that expire, that link to 3 different files before linking to the video, and make those hashes expire after a sort time. So: request->file->file->file->videoLink

      Reply
  2. Cameron

    We have a Windows 2003 with IIS6.0/PHP/MySQL environment and our environment does not have an .htaccess file.
    The goal is to protect our .mp4 video files using the code outlined above and we would appreciate your help with this.
    Thank you,
    Cameron

    Reply
    • Tom

      Hi Cameron, I'm afraid I don't have any experience with IIS so I can't be of much help, I should imagine there's some way of denying access to all files of a certain type?

      Reply


~ Comments are now closed ~

Get in touch here