Create RESTful Applications Using The Zend Framework - Part III : Managing API Key

In the first two posts of this series, we discussed how to route REST requests to controllers and return HTTP response code. In this article I will talk about managing API keys.

Having the clients send API key within the HTTP header is convenient to handle. We can quickly check the HTTP request header and decide whether to allow or deny the request.

As a prerequisite you should be familiar writing front controller plugins. Let's write a front controller plugin that does the following:

  • Check whether the request has the correct API key in the HTTP header
  • If the correct API key in the HTTP header is missing, set the appropriate HTTP response code and route the request to error controller

We will not perform any changes to our previous article controller.

Let's start writing code.

Step 1: Write the front controller plugin.

Create the directory library/My/Controller/Plugin.

Inform the autoloader about the namespace 'My'. Also, register the plugin "My_Controller_Plugin_RestAuth". Let's add these lines to our configuration file application/configs/application.ini generated by Zend_Tool(or the quick start guide).

autoloadernamespaces.1 = "My_"
resources.frontController.plugins = "My_Controller_Plugin_RestAuth"

Let's write the front controller plugin. Create and edit the file library/My/Controller/Plugin/RestAuth.php


<?php
class My_Controller_Plugin_RestAuth extends Zend_Controller_Plugin_Abstract
{
    public function 
preDispatch(Zend_Controller_Request_Abstract $request)
    {
        
$apiKey $request->getHeader('apikey');

        if (

$apiKey != 'secret') {
            
$this->getResponse()
                    ->
setHttpResponseCode(403)
                    ->
appendBody("Invalid API Key\n")
                    ;
            
$request->setModuleName('default')
                        ->
setControllerName('error')
                        ->
setActionName('access')
                        ->
setDispatched(true);

        }

    }

}

?>

In our front controller plugin, we hook into the preDispatch() method. Before a controller action is executed, our Predispatch() method is called.
In our preDispatch() method, we check whether the HTTP request header contains the key 'apikey' with value 'secret'. If the request does not have the correct API key we set the 403 HTTP response code and set the controller and action to 'error' and 'access' respectively. We also set the dispatched status to true.

Step 2: Add the 'access' action to the error controller in application/controllers/ErrorController.php.

<?php
public function accessAction()
{
        
$this->_helper->ViewRenderer->setNoRender(true);
}

?>

In the sample code, we simply disable the view. In your application, you could perform logging in the access action of the error controller.

Step 3: Let's test our application using cUrl.

Make a request without sending the 'apikey' HTTP header.

curl http://zfrest.example.com/article -v

The sample output of the above command looks like:

* About to connect() to zfrest.example.com port 80 (#0)
*   Trying 127.0.0.1... connected
* Connected to zfrest.example.com (127.0.0.1) port 80 (#0)
> GET /article HTTP/1.1
> User-Agent: curl/7.19.7 (i386-redhat-linux-gnu) libcurl/7.19.7 NSS/3.12.4.5 zlib/1.2.3 libidn/1.9 libssh2/1.2
> Host: zfrest.example.com
> Accept: */*
> 
< HTTP/1.1 403 Forbidden
< Date: Mon, 01 Mar 2010 08:46:03 GMT
< Server: Apache/2.2.14 (Fedora)
< X-Powered-By: PHP/5.3.1
< Content-Length: 16
< Connection: close
< Content-Type: text/html; charset=UTF-8
< 
Invalid API Key
* Closing connection #0

We recieved the HTTP response code 403 with the body content 'Invalid API Key'.

Let's make another test. This time, let's add the correct API key in the request HTTP header.

curl -H "apikey: secret" http://zfrest.example.com/article -v

Using the -H switch, we can send headers.

The output :

* About to connect() to zfrest.example.com port 80 (#0)
*   Trying 127.0.0.1... connected
* Connected to zfrest.example.com (127.0.0.1) port 80 (#0)
> GET /article HTTP/1.1
> User-Agent: curl/7.19.7 (i386-redhat-linux-gnu) libcurl/7.19.7 NSS/3.12.4.5 zlib/1.2.3 libidn/1.9 libssh2/1.2
> Host: zfrest.example.com
> Accept: */*
> apikey: secret
> 
< HTTP/1.1 200 OK
< Date: Mon, 01 Mar 2010 08:57:15 GMT
< Server: Apache/2.2.14 (Fedora)
< X-Powered-By: PHP/5.3.1
< Content-Length: 20
< Connection: close
< Content-Type: text/html; charset=UTF-8
< 
* Closing connection #0
all articles content

This time we received the HTTP response code 200 with body content 'all articles content'.

Reference:
Writing front controller plugins

Taxonomy upgrade extras: 

Comments

Shouldn't an X-.... http custom header being used?

Giorgio,

Thanks for the comment.

Custom HTTP header x-apikey makes more sense.

Hi Sudheer,
This is a great stuff and thanks for the POST. I tried implementing, and it worked great!!!

Thanks !!

I have a query for you, can we expand this functionality to work together with Zend_OAuth also? If so, pls give a small example. Your suggestion will be a great help for me.

Hi Vineela,

You're welcome.

I wouldn't recommend extending this example to use OAuth. I will try to cover Zend_Oauth in a future article.

Thanks for the comment.

Hi Sudheer,

Will be eagerly waiting for a sample Application using Zend_OAuth.

Thanks,

your post is too nice and it helped me a lot
mcse

Can you please explain me the curl commands for PHP ?
The tutorial is great, but would have been better using actual php curl calls, rather than the shell.

Thanks.

I will cover writing REST clients using PHP in the upcoming posts.

First, thanks for the articles.

I'm very interested in this as well. For example, let's say I want to write a PHP client to interact with my REST API via POST. What should the postAction do if the supplied data is invalid? I understand sending out a response code (400?), but what should the response body contain? Ideally it would contain the supplied values for each param and any associated error codes, but I'm not sure how to do this. I'm also not sure how to then use that data. For example, let's say my client was a ZF controller with a Zend_Form in it. My controller posts to my REST controller via curl, and receives a response with errors ... how do I use the response to populate my form/view? The same could be asked if I posted via ajax, but updating view would have to be done via JS, so that may be too much to cover.

Overall, I think it would be great if the next article focused on actually building a client to interact with the api. Good stuff.

Thanks for the comment.

It is hard to answer all the questions in a comment reply. Like you say, the next few posts in this series should cover returning responses in detail.

Hi Rob, This REST Client code may be useful to you.

<?php
/**
*
* THIS IS AN EXAMPLE AS HOW TO CALL A REST CALL - POST METHOD
*/
$url = 'http://www.helloworld.com/article';
$fields_string = '';
$ch = curl_init();
$headers[] = array('apikey' => 'secret');
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("apikey: secret")); // this is for the API key
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch,CURLOPT_POST,'');
curl_setopt($ch,CURLOPT_POSTFIELDS,$fields_string);
$result = curl_exec($ch);
curl_close($ch);

echo($result).'';
?>

<?php
/**
* this is to make GET call
**/
$url = 'http://www.helloworld.com/article/1';

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("apikey: secret")); // this is for the API key
$result = curl_exec($ch);
curl_close($ch);
echo($result).'';
?>

<?php
/**
* THIS IS TO HAVE A PUT CALL
**/
/**
$xml = 'Hello World! Watch me PUT this here.';
$fh = fopen('php://memory');
fwrite($fh, $xml);
rewind($fh);
**/
$ch = curl_init('http://www.helloworld.com/article');
curl_setopt($ch, CURLOPT_HTTPHEADER, array("apikey: secret")); // this is for the API key
curl_setopt($ch, CURLOPT_PUT, true);
//curl_setopt($ch, CURLOPT_INFILE, $fh);
//curl_setopt($ch, CURLOPT_INFILESIZE, strlen($xml));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

echo curl_exec($ch);

curl_close($ch);
//fclose($fh);
?>

<?php
/**
* THIS IS FOR HTTP - DELETE
**/
$request = 'http://www.helloworld.com/article/1';

$ch = curl_init();
curl_setopt ($ch, CURLOPT_URL, $request);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("apikey: secret")); // this is for the API key
curl_setopt ($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)");
//curl_setopt ($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
//curl_setopt ($ch, CURLOPT_USERPWD, $this->requestLogin);
curl_setopt ($ch, CURLOPT_HEADER, 0);
curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt ($ch, CURLOPT_HTTPHEADER, Array("Content-Type:application/atom+xml"));
curl_setopt ($ch, CURLOPT_FAILONERROR, 1);
curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "DELETE");
echo curl_exec ($ch);

?>

Thanks for the helpful series of REST articles! Sudheer.

Similar to 403 Forbidden, but specifically for use when authentication is possible but has failed or not yet been provided. The response must include a WWW-Authenticate header field containing a challenge applicable to the requested resource.

http://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_Client_Error

Your series of posts are very useful... first iam appreciating your efforts.

I have used the GET type of curl request, and i want the response as xml foramt... iam using Zend Rest Controller... how can i achieve this.

You could create the XML document and pass it on to the view. Optionally, you can directly send the response from the controller. If I were to send a lot of XML documents, I would make an action helper.

Thanks for your reply sudheer.

I want to know how to retrieve the username and password for validating a user.

See, we are sending the credentials as part of a curl request (CURLOPT_USERPWD) and how can we retrieve these values in our next page, and where we need to check these values, in database or any other source, for verification?

Please help :)

One of the REST constraints is statelessness. No client context is stored on the server between requests. You have to send the credentials in every request.

Thanks for ur answer sudheer, i actually want to know how do i fetch the username and password that i pass as request to a page, through curl. And i got my solution here:

http://stackoverflow.com/questions/4942417/curl-http-authentication-with...

Add new comment

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
By submitting this form, you accept the Mollom privacy policy.