Wednesday, May 23, 2012

Quick and Dirty REST Security (or Hashes For All!) - blog.phpdeveloper.org » PHP

Quick and Dirty REST Security (or Hashes For All!) - blog.phpdeveloper.org » PHP:
So in working up a new RESTful service I’ve been tinkering with, I wanted to provide some kind of “authentication” system for it. I started to look into OAuth, but got a bit overwhelmed by everything that was involved with it. Looking for something a bit more lightweight (and simpler to implement a bit more quickly) I came across this older article with a suggestion of a private key/hash combination. I figured that could do the job nicely for a first shot, so I set to implementing it.


On the Server Side


I’m using the FuelPHP framework for this one, but that’s really only giving me a structure to work in and pull the request information from. This would work in most major frameworks (and even outside of one if you you’re a “do it my way” kind of developer). First off, let’s start with the controller side:
<?php
class Controller_User extends Controller_Rest
{
protected function validateHash()
{
$request = file_get_contents('php://input');
$requestHeaders = apache_request_headers();

if (!isset($requestHeaders['X-Auth']) || !isset($requestHeaders['X-Auth-Hash'])) {
$this->response('fail!',401);
} else {
// we have the headers - let's match!
$user = Model_User::find()->where('public_key',$requestHeaders['X-Auth'])->get_one();

if ($user !== null) {
$hash = hash_hmac('sha256',$request,$user->private_key);
return ($hash == $requestHeaders['X-Auth-Hash']) ? true : false;
} else {
return false;
}
}
}

public function post_index()
{
// return the user details here....
}

public function router($resource, array $arguments)
{
if ($this->validateHash() == false) {
$resource = 'error';
$arguments = array('Not Authorized',401);
}

parent::router($resource,$arguments);
}
}
?>

There’s a lot going on here, so let me walk you through each of the steps:

  1. First off, we’re making a RESTful service, so we’re going to extend the Controller_Rest that Fuel comes with. It has some handy special routing. Our POST request in the example below would try to hit the “post_index” method and have its hashes checked in the process.

  2. Next up is the “validateHash” method – this is where the hard work happens:

    • The request and headers are read into variables for easier use ($request and $requestHeaders).

    • It then checks to be sure that both of our required headers are set (X-Auth and X-Auth-Hash). There’s nothing magical about these header names, so they can be switched out depending on need and naming preference.

    • If they’re there, the next step is to find the user based on the public key that was sent. This value is okay to openly share because, without the private key to correctly hash the data, your requests will fail.

    • The hash_hmac function is then used (with the “sha256″ hash type) to regenerate the hash off of the contents of the request and the private key on the found user.



  3. If all goes well, the request continues on and the “post_index” method is used. If it fails, however, the check in the “route” method of the controller makes a switch. It changes the currently requested resource to “/error/index” instead of what the user wants. This seamlessly shows the user a “Not Authorized” error message (401) if the hash checking fails.



A Client Example


Now, to help make it a bit clearer, here’s an example little script showing a curl request using the hashes:
<?php

$privateKey = 'caa68fb2160b428bd1e7d78fcf0ce2d5';
$publicKey = '01fa456c4e2a2bc13e5c0c4977297fbb';

$data = '{"username":"happyFunBall"}';
$hash = hash_hmac('sha256',$data,$privateKey);

$headers = array(
'X-Auth: '.$publicKey,
'X-Auth-Hash: '.$hash
);

$ch = curl_init('http://mysite.localhost:8080/user');

curl_setopt($ch,CURLOPT_HEADER,true);
curl_setopt($ch,CURLOPT_HTTPHEADER,$headers);
curl_setopt($ch,CURLOPT_POSTFIELDS,$data);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);

$result = curl_exec($ch);
curl_close($ch);

print_r($result);
echo "\n\n";
?>

You can see that both the public and private keys are specified (but on the PHP side, not visible to the user) and are sent as the “X-Auth*” headers as a part of the request. In this case, we’re POSTing to the &
Truncated by Planet PHP, read more at the original (another 733 bytes)

DIGITAL JUICE

No comments:

Post a Comment

Thank's!