API header-based authentication in Zend Framework 2

Based on a Twitter conversation I promised to blog about how we deal with API authentication. In short, you have a few formats: if HTTP Basic and HTTP Digest are no option and OAuth2 is not a good candidate either, you can choose to sign your requests with a signature and send the signature in a header.

In the case of an application we developed at Soflomo, we required authentication with an iOS app consuming our RESTful API. The process of OAuth2 was for several reasons not applicable (which I won't cover in this blog post) so we opted for authentication based on AWS signing & authentication. A big benefit of this method is that it does not only provide the authentication, but also the integrity of the request (nonetheless we used TLS, but you cannot have enough layers of security).

The AWS Signing & Authentication method does work with a private and public key. The public key is sent as (public) identifier for the request and the private key is used for a cipher which is send as signature for the request. The public key and signature are used in a header string which looks as follows:

Authentication: <Prefix> <PublicKey>:<Signature>

The header Authentication contains a string with a prefix, a space, a public key, a colon and then the signature. In our Zend Framework 2 application we validate the header according to the following steps:

  1. We check if the header is present
  2. We check if the prefix is a valid prefix
  3. We check if the header contains a <PublicKey>:<PrivateKey> part
  4. We generate a signature based on the request and compare it with the given signature from the request
All these steps are extremely simple to implement with event listeners in Zend Framework 2. I will not go into detail how to check every step, but the basic outline of the Zend Framework 2 structure is written below.

Identify API controllers

In our application we have more than an API, so we need to check the Authorization header only on the API calls. Using the Event Manager of Zend Framework, we benefit from the shared event manager and the multiple identifiers a controller's event manager can have. (If you don't know how the event manager & shared event manager work, I suggest your read the manual first).

By default, a controller in Zend Framework 2 has multiple identifiers:

All our API controllers extend from a single abstract controller, which defines an identifier for all implemented controller classes:

namespace MyApp\Api\Controller;

use Zend\Mvc\Controller\AbstractRestfulController;

abstract class AbstractController
    extends AbstractRestfulController
{
    protected $eventIdentifier = 'MyApp\Api\Controller';
}

Now we can listen to the dispatch event of every controller in the api.

Create an authentication adapter

Next we dive to the other end of the authentication: assume you hook into the application event process somewhere and you need to validate the incoming request. Zend Framework 2 provides a set of authentication mechanisms, all under Zend\Authentication. A custom adapter implementation can look like this:

namespace MyApp\Api\Authentication\Adapter;

use Zend\Authentication\Adapter\AdapterInterface;
use Zend\Authentication\Result;
use Zend\Http\Request;

use MyApp\Repository\UserRepository;

class HeaderAuthentication implements AdapterInterface
{
    protected $request;
    protected $repository;

    public function __construct(
        Request $request,
        UserRepository $repository
    ) {
        $this->request    = $request;
        $this->repository = $repository;
    }

    public function authenticate()
    {
        // authenticate the request here
    }
}

The constructor accepts two arguments: first, the current request is injected so authenticate() can use the request to validate its headers. Secondly, a UserRepository object is used to find the private key based on the public key part.

In the authenticate() method we should validate the request. Implementing the AdapterInterface, the authentication method must return a Zend\Authentication\Result instance.

public function authenticate()
{
    $request = $this->getRequest();
    $headers = $request->getHeaders();

    // Check Authorization header presence
    if (!$headers->has('Authorization')) {
        return new Result(Result::FAILURE, null, array(
            'Authorization header missing'
        ));
    }

    // Check Authorization prefix
    $authorization = $headers->get('Authorization')
                             ->getFieldValue();
    if (strpos($authorization, 'PRE') !== 0) {
        return new Result(Result::FAILURE, null, array(
            'Missing PRE prefix'
        ));
    }

    // Validate public key
    $publicKey = $this->extractPublicKey($authorization);
    $user      = $this->getUserRepository()
                      ->findByPublicKey($publicKey);
    if (null === $user) {
        $code = Result::FAILURE_IDENTITY_NOT_FOUND;
        return new Result($code, null, array(
            'User not found based on public key'
        ));
    }

    // Validate signature
    $signature = $this->extractSignature($authorization);
    $hmac      = $this->getHmac($request, $user);
    if ($signature !== $hmac) {
        $code = Result::FAILURE_CREDENTIAL_INVALID;
        return new Result($code, null, array(
            'Signature does not match'
        ));
    }

    return new Result(Result::SUCCESS, $user);
}

In the above piece of code, the most methods should be self explanatory. The only thing that is important is the getHmac() method. The getHmac() generates a HMAC (keyed-hash message authentication code) based on the request and compares the generated HMAC with the given signature. Basically, we use the HMAC as signature for the request. If our calculated signature does not match the given signature in the Authorization header, the request has probably been changed and should not be accepted.

From our experience, a few things are important to consider. First, the signature helps to validate the integrity of the complete request. So this includes your headers, body and query parameters! If you do not include the query parameters, you could modify the query without the need to modify the signature.

Furthermore, include the request method and the path in your signature, for the same reason as above. To make it yourself easy, just use the first line of a HTTP request:

POST /foo/bar?baz=bat HTTP/1.1

Regadering headers, use at least the Date header. Check the value of the date header separately and make sure the date is not a date before a given timestamp (i.e. only requests within 15 minutes are accepted). This ensures and old request cannot be send again exactly in the same way.

Combine all these parts (request line + some important headers + request body) and create a HMAC token for it. This should match the signature in the request.

Listen to the controllers' dispatch

Now it is time to link all the parts together: when a controller with the given event manager's identifier is dispatched, check the authentication of the given request.

This is done by a listener. A listener could look like this:

namespace MyApp\Api\Listener;

use MyApp\Api\Authentication\Adapter\HeaderAuthentication;
use Zend\Mvc\MvcEvent;

class ApiAuthenticationListener
{
    protected $adapter;

    public function __construct(
        HeaderAuthentication $adapter
    ) {
        $this->adapter = $adapter;
    }


    public function __invoke(MvcEvent $event)
    {
        $result = $this->adapter->authenticate();

        if (!$result->isValid()) {
            $response = $event->getResponse();

            // Set some response content
            $response->setStatusCode(401);
            return $response;
        }

        // All is OK
        $event->setParam('user', $result->getIdentity());
    }
}

And register this listener in your Module's onBootstrap() method:

public function onBootstrap(MvcEvent $event)
{
    $app = $event->getApplication();
    $sm  = $app->getServiceManager();
    $em  = $app->getEventManager();

    $listener = $sm->get('MyApp\Api\Listener\ApiAuthenticationListener');
    $em->getSharedManager()->attach('MyApp\Api\Controller', 'dispatch', $listener);
}

You can obviously apply some more logic in the listener (e.g. we allow to skip the Authentication header in some cases), but this is the most basic example. To adhere to the single responsibility pricinple, it is a good idea to have the authentication logic separated from the logic which hooks into the MVC process.

In our application, we do not only return a 401 status code, but also return a JSON message according to the application/problem+json draft for HTTP problems. Also this should be separated from the authentication adapter itself and the listener is a perfect place for this.

Glue all the parts together

Now to conclude we have an authentication adapter where "proper" DI is required to inject the response and an entity repository. For the api listener, the api adapter itself should be injected. So, we need the service manager.

For the authentication adapter, a factory could look like this:

namespace MyApp\Api\Factory;

use MyApp\Api\Authentication\Adapter\HeaderAuthentication;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class AuthenticationAdapterFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $sl)
    {
        $request     = $sl->get('Request');
        $respository = $sl->get('MyApp\Repository\UserRepository');

        $adapter = new HeaderAuthentication($request, $repository);
        return $adapter;
    }
}

And for the MVC listener:

namespace MyApp\Api\Factory;

use MyApp\Api\Listener\ApiAuthenticationListener;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class AuthenticationListenerFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $sl)
    {
        $name    = 'MyApp\Api\Authentication\Adapter\HeaderAuthentication';
        $adapter = $sl->get($name);

        $listener = new ApiAuthenticationListener($adapter);
        return $listener;
    }
}

As a final step, register both factories in the services configuration and you're all set up!

services => array(
    'factories' => array(
        'MyApp\Api\Authentication\Adapter\HeaderAuthentication' => 'MyApp\Api\Factory\AuthenticationAdapterFactory',
        'MyApp\Api\Listener\ApiAuthenticationListener' => 'MyApp\Api\Factory\AuthenticationListenerFactory',
    ),
)

Conclusion

With the above steps, it is fairly easy to check the authentication and validity of a request at your API. The method used is similar how Amazon checks the requests in all of its RESTful APIs and is proven by many others. By using the Zend\Authentication base and a listener which hooks into the MVC process, both parts are loosly coupled and perfectly usable outside the context of a single application.