Skip to content

Instantly share code, notes, and snippets.

@nevali
Created March 8, 2010 22:03
Show Gist options
  • Save nevali/325815 to your computer and use it in GitHub Desktop.
Save nevali/325815 to your computer and use it in GitHub Desktop.
User Agent/Referrer Verification
<?php
/* Client sample implementing User Agent/Referrer Verification */
/* Generate $key based on the contents of this resource */
$referrer = 'http://example.com/page';
/* This is our User-Agent string. The server must be expecting requests from this User-Agent. */
$ua = 'My User Agent/1.0';
/* Note that the client must have access to $referrer in order to generate the sha256-hmac */
$key = hash_hmac_file('sha256', $referrer, $ua);
$ch = curl_init('http://example.com/some-resource');
/* Send $key and the referrer URL along with our request; the server
* will check and verify each. If we don't have access to http://example.com/page to generate
* the proper key, our value of $key will be invalid and the request will fail.
* We don't actually send the User Agent itself.
*/
curl_setopt($ch, CURLOPT_HTTPHEADER, array('X-Verification-Key: ' . $key));
curl_setopt($ch, CURLOPT_REFERER, $referrer);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
$resource = curl_exec($ch);
echo $resource;
<?php
/* Server utility: cache a list of valid User Agent/Referrer Verification keys */
/* Build the $keys array, exactly as in server.php */
require_once('server-keys.php');
/* Store the contents of $keys in a file. This file is read by server.php if it's present,
* rather than re-generating the list of keys on every request.
*
* Note! Ensure the web server forbids access to this file to clients, but that the user the
* web server runs as can read it. The simplest way to do this is to alter the path to put it
* outside of the DocumentRoot (or whatever your server's equivalent is), but still ensure it's
* world-readable.
*/
if(!($f = fopen('key-cache.json', 'w')))
{
echo "fatal: failed to open key-cache.json for writing\n";
exit(1);
}
fwrite($f, json_encode($keys));
fclose($f);
<?php
/* Server include: generate a list of valid User Agent/Referrer Verification keys */
/* Valid user agents */
$validUAs = array('My User Agent/1.0', 'Genuine Media Player 0.0.1');
/* Names of files which exist on the server and will be considered valid referrers */
$validReferrers = array('page' => 'http://example.com/page');
$keys = array();
/* $keys is an associative array, where the array key is the sha256-hmac of the content of the
* referring file and the HMAC key is the User-Agent string we expect the client to send.
* Thus, for each combination of the valid user agents and referrers, generate an HMAC.
* Each array value is itself an array made up of the actual referring URL and user-agent itself.
*
* The client can only generate the same key if its User-Agent matches one of the ones in
* $validUAs, and it has access to the same referring file. If for some reason it can't access
* it (for example, it requires a user name and password the client doesn't have), it won't
* be able to generate the correct key.
*/
foreach($validReferrers as $ref => $refURL)
{
foreach($validUAs as $ua)
{
$keys[hash_hmac_file('sha256', $ref, $ua)] = array($refURL, $ua);
}
}
<?php
/* Server sample implementing User Agent/Referrer Verification */
if(file_exists('key-cache.json'))
{
/* A key cache exists; just load and use it */
$keys = json_decode(file_get_contents('key-cache.json'));
}
else
{
/* Build a $keys array from scratch */
require_once('server-keys.php');
}
/* This will vary by SAPI; we don't really want to require a POST, nor do we want the key
* showing up in proxy logs, so we do it with a custom header.
*/
$headers = apache_request_headers();
$valid = false;
/* Check that the verification key, “referer” and user-agent headers are all present */
if(isset($headers['X-Verification-Key']) && isset($headers['Referer']))
{
/* Look the verification key up in our list; check, just to make sure, that the
* referrer and sent by the client matches the one which generated the key.
* This extra check doesn’t really achieve much, as the client can set the Referer
* to anything it likes, but being strict never hurt anybody.
*/
if(isset($keys[headers['X-Verification-Key']]) && $keys[headers['X-Verification-Key']][0] == $headers['Referer'])
{
/* Everything matches up, allow the resource to be served to the client */
$valid = true;
}
}
if(!$valid)
{
header('HTTP/1.0 403 Forbidden');
echo "Forbidden.\n";
exit();
}
echo "Success. This is the protected resource.\n";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment