FuelPHP

Quick and Dirty REST Security (or Hashes For All!)

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]
<?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);
}
}
?>
[/php]

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]
<?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&#8217;);

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 "nn";
?>
[/php]

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 “/user/enygma” resource and creating a user. To create the value to put into $hash, we use the same “sha256” hashing method and salt it with the private key. This value – the result of the data+private key hashing – is then passed to the service who uses the controller code from above to validate that it’s correct.

It’s not a true “login” process, but it can help to validate that a piece of information known only to the user (the private key) and never shared directly, is known and matches the requests that the user sends.

You can find the code for this in this gist if you’d like something a bit more readable: https://gist.github.com/2697434

Advertisement

Behat + FuelPHP = RESTful Testing Happiness

If you’ve been following my recent posts, you know I’ve been working more lately with Behat for resting some REST services. In this post I showed you how to get things set up for some testing. In this post, I’ll show you how to use a custom class that I’ve put together to make a reusable system for testing REST.

For those that want to cut to the chase, I’ve posted some example code to github showing the code for the two different sides of the equation – the Behat testing and the PHP framework side (I went with FuelPHP because I was already familiar with it and it makes RESTful interfaces dead simple). The key is in the FeatureContextRest.php file that uses the Guzzle client to make the HTTP requests over to the framework. It provides some handy contexts that make it easy to create Scenarios.

So, still with me? Good – I’ll show you how to get my sample scripts installed so you can see my example in action. In the repository you’ll find two different directories – “fuel” and “behat”. In each you’ll find some files:

behat

  • features/index.features:
    An example set of Scenarios that use the REST testing contexts to create, find and deleting a “User” from the system
  • features/bootstrap/FeaturesContextRest.php:
    The key to the puzzle, the implementation of some context methods for use in Behat

fuelphp

  • fuelphp/classes:
    These are the example models and controllers you can drop into your Fuel install to have them “magically” work (with any recent version of the framework).

This is assuming you already have Behat installed and working and have set up FuelPHP on a host somwhere. For our example, we’ll just use http://behat-test.localhost:8080&#8221; Here’s what to do:

  1. Start by copying over the FeaturesContextRest.php file to the “features/bootstrap” directory of your Behat installation alongside the default FeaturesContext.php.
  2. Copy over the behat.yml configuration file into your testing root (the same level as the “features” directory) and update the base_url value for your hostname.
  3. Take the “classes” directory and copy over its contents to the location of your FuelPHP installation – if you’re familiar with the structure of the framework, this is simple…it just goes in “app/classes”.
  4. You’ll need a database to get this working, so you’ll need to get the FuelPHP ORM stuff configured and working in your install. Be sure to update your config.php to automatically load the “orm” module.
  5. Make a database for the project and use the init.sql to create the “users” table.
  6. To test and be sure our models/controllers are working right, hit your host in a browser, calling the “User” action like: “http://behat-test.localhost:8080/user&#8221;. This should call what’s in Controller_User::get_index(). It’ll probably just return an empty result though, since there’s nothing for it to pull.
  7. To use the FeaturesContextRest.php, be sure that you include that file into your default FeaturesContext.php file and that your class “extends FeaturesContextRest”.

So, if all has gone well, you have all the pieces in place to get started. Let’s start with a sample Scenario to show you how it all works:

Scenario: Creating a new User
	Given that I want to make a new "User"
	And that its "name" is "Chris"
	When I request "/user/index.json"
	Then the response is JSON
	And the response has a "userId" property
	And the type of the "userId" property is numeric
	Then the response status code should be 200

If you’ve used Mink in the past, so of this will seem familiar. This doesn’t use Mink, however, and I’ve opted to use Guzzle as the client for the backend to replace some of the browser interactions. Really, we don’t need all of the fancy interface interaction Mink gives, so this streamlined interface is more useful.

In this example, we’re creating a user with a property “name” of “Chris” and sending that off to the “/user/index.json” endpoint. A few tests are included like: checking the response code, seeing if the return value has a “userId” property, etc. If all went well and the user was created (this is a POST, so it calls “post_index” in the controller), this test should pass with flying colors….green, hopefully. 🙂

The key is in the phrases “to make a new” (POST), “to find a” (GET) and “to delete a” (DELETE) to tell the tests which verb to use.

Hopefully this library will be useful to some folks out there – I’m going to be improving it as I work on more tests for some current projects to ensure better quality of the software.

Uniqueness in Fuel Models

As a part of broadening my horizons, I’m back to working with Fuel, a framework that has a familiar feel to some of the ones I’ve used in the past but has some extra “oomf” from new features and PHP 5.3-ness.

I was working in my models the other day and had a need for uniqueness – I wanted to be sure that the object I was pushing into the database didn’t match any other one. I went through the docs and didn’t see anything about it and a glance through the source didn’t turn up anything either. So, as an alternative, I came up with a method I put in my base model:

[php]
class Model_Base extends OrmModel
{
public function isUnique($modelObject)
{
$modelType = get_class($modelObject);
$found = $modelType::find(‘all’,array(
‘where’ => $modelObject->to_array()
));
return (count($found)>0) ? false : true;
}
}
[/php]

The code above finds the class name (the model the object is made from) and tries to find anything with exactly the same properties. The “to_array()” method is something Fuel has to translate objects into handy arrays.

Fuel ORM docs