Preventing Unwanted Access to Your API
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.


8 comments to “Preventing Unwanted Access to Your API”
January 25th, 2007 at 2:49 pm
There is little difference between this problem (of someone using your API) and the security vulnerabilities opened up by Cross Site Request Forgeries.
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.
January 26th, 2007 at 1:45 am
Actually it’s slightly different. The token is the session ID in Cal’s case but it’s generated in Chris’s case. This saves a little load on your session backend. *shrug*
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.
January 26th, 2007 at 3:25 pm
"Actually it’s slightly different. The token is the session ID in Cal’s case but it’s generated in Chris’s case."
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.
January 29th, 2007 at 10:35 pm
You’re right. I didn’t look at Cal’s code too closely.
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.
February 1st, 2007 at 6:37 pm
If a user has two pages open from your app at once, this will break for all but the most recently opened one.
February 23rd, 2007 at 8:47 am
….let’s say 2 mozilla tabs, then you can prevent this method from breaking, by storing your key in a cookie…
April 24th, 2008 at 8:20 pm
I am new to this ajax security. I might be asking a wrong question.
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
June 13th, 2008 at 1:21 pm
Locks help keep honest people honest, this little bit of obfuscation helps keep honest people out of your API – it raises the difficulty bar a bit and often enough, that added bit of difficulty is enough to dissuade the mildly curious. The author stated clearly, this was not foolproof and could be defeated by the determined, just as even very secure locks on other things can be defeated, by the determined. Take that for what it is worth, don’t throw rocks out of turn.