Ok, so you've written a cool new Web API and you've written l33t JavaScript to call it from your page. The problem is, anybody who views your source can see how you call your new toy and use it for their own nefarious purposes. Granted, sometimes this is what you want but there are times when you want to keep your toys to yourself. For those times, here is a quick trick you can implement that will help thwart most evil doers.
It should be noted that this technique, like many security measures, is not foolproof, it can be circumnavigated by the determined. It should be used as part of a multi-faceted security strategy.
Summary for the Impatient
For those anxious to skip ahead, here's the gist of the idea. You store a secret in your $_SESSION. Then you place that value in your JavaScript. When you make calls back to your API, you pass this secret back as a parameter. Your API checks it against the $_SESSION and if they match, you know that the API was called from your web page.
Details for the Patient
The one thing that people hijacking your API don't have access to is information stored in your $_SESSION. This concept works on the basis that only your code has access to your $_SESSION and that we store a secret in there and then rotate it often. In practice, it's pretty easy.
In your PHP code create a value in the session, we will call it ajaxKey.
<?PHP
$_SESSION['ajaxKey'] = md5(mktime());
?>
In the above example, I'm using the md5() of the current time. While this will work, it is a predictable value therefore it is not the most secure secret to use. In a production environment, I would want something random. The idea is to create something that is not easily guessed.
Once you have stored your secret you need to let your webpage know what it is. In your web page, find a convenient place in your JavaScript code and put something like this:
var ajaxKey = '<?PHP echo $_SESSION['ajaxKey']; ?>';
Now both your API and your JavaScript know the secret. This works because API calls to the server are calls from the same browser therefore both have access to the same $_SESSION.
In your ajax call to your API, pass the secret back as a parameter. Here's an example using prototype's Ajax class.
var myAjax = new Ajax.Request(
"http://example.com/myAPI.php?ajaxKey="+ajaxKey,
{
method: 'get',
onComplete: displayData,
});
In your API code, you will check first for the existence of the parameter ajaxKey and then check it for a match with the $_SESSION. If they don't match then immediately fire your photon torpedoes at the intruder and slam the door.
By changing this on every new page call, you effectively expire old keys and prevent people from caching them for later use.
That's it, a simple but effective way you can protect your API from unwanted usage. As I stated at the beginning, it is not fool-proof. There are other ways of protecting your API such as a login and password check. Those however require not only more code but management.


Comments (Login to leave comments)
If you read Chris Shiflett's article on <a href="http://shiflett.org/articles/cross-site-request-forgeries">CSRF</a> and scroll down Safeguarding Against CSRF you'll see exactly the same solution.
I think we should work towards implementing this type of protection on all of our request processing code, and not just their javascript-called APIs.
By this same argument, I could just accuse Chris's anti-CSRF as being just a knock off of the standard operating procedure to prevent double-submission of forms.
Think about that one.
Are you sure?
// Cal's code
<?PHP
$_SESSION['ajaxKey'] = md5(mktime());
?>
...
<script>
var ajaxKey = '<?PHP echo $_SESSION['ajaxKey']; ?>';
</script>
// Chris's code
<?PHP
$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;
?>
...
<form action="buy.php" method="post">
<input type="hidden" name="token" value="<?php echo $token; ?>" />
</form>
Chris just uses a variable $token to hold the secret value, instead of refencing it from the $_SESSION array.
Both generate a secret token variable, which is stored inside the $_SESSION. Only requests that contain the secret token are valid.
It's just a different spin on the same principal - which is great if it gets more people thinking about controlling the requests made to their scripts.
It does seem silly. Why not just use session_id() instead of a secret token bound to session id? They're the same in terms of security.
My guess is both are copying the double-form submission trick (circa 2001). In this trick, you need a token bound to the session instead of using the session id because of the need to handle multiple forms and expire old submission tokens or honest double submissions of the same form.
Suppose I visit such a page, I can see this ajaxKey. Then I can just write a page and hardcode this key, and put the response text into a text box, then I get your API?
If I need to break the same-origin issue, I just write a proxy.
I am wrong?
Thanks