Protect your assets with expiring URLs

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!

Don’t vet Passwords!

I've designed loads of forms in my time, some for collecting random data, some for logging in, and some for user sign up. I usually employ form validation to make sure it's a valid email, and a valid phone number etc, but I never check the password (unless it's for length). I myself like to sign up to each and every service I come across and generally I'm satisfied with what I get in terms of form design. However there are some websites that tell me that my password can only contain letters and numbers! Most of my passwords are 17 characters or longer, with letters, numbers and symbols, so when a site tells me I should effectively make my password less secure I get really annoyed.

Companies like Google and Microsoft never stop telling up to make our passwords more secure, Facebook and Twitter say the same, so why on Earth should websites be asking us alter them? I for one cannot see why developers are intent on doing this? MySQL Injection - no! Unless of course they've never heard of mysql_real_escape_string(), but I doubt that.

So what I really want to say is this - don't ask users to exclude certain components from their passwords! And if you know the reason for this travesty, please feel free to comment and make me look like an idiot 😉