Securing your PHP REST API with OAuth

Problem:

There are many considerations to make when building and securing a public-facing API, but I wanted to look at one issue in particular: Making sure that only authenticated clients can access it.

The aim of this tutorial is to create a flow where a client only needs to make one request to the server to get back a response. In this simple application flow, we can avoid the added complexity of managing request and/or access tokens:

  1. Client sends a request for data along with their authentication details
  2. Server sends data back to the authenticated client

This flow will be familiar if you have used Amazon Web Services (AWS) before, as they use a similar “signed-message” system.

Solution:

Use a simple version of the OAuth 1.0a standard which is known as “2-legged” authentication. In this system, no access tokens are used. Authentication is performed using a public and private key system. These public and private keys are known by the client (consumer) and the server (provider).

  • The private key (the secret) is NEVER passed over the wire
  • The provider uses the private key within a hash-based message authentication code (HMAC) to generate a signature
  • The consumer uses its own copy of the private key to verify the signature is authentic
  • You should use always SSL/TLS to encrypt traffic between the consumer and provider

My solution uses PHP OAuth library by Andy Smith (MIT licence) for the heavy lifting. I have made this library available as a Composer package which can be installed like this:

$ php composer.phar require glenscott/oauth

Here are two simple examples for the provider and consumer sides:

Provider side

In this example, the list of valid consumer keys and secrets are hardcoded, but you probably want to store these in a DB somewhere. The provider will return "true" if it is a valid authenticated request, or otherwise it will spit out and error message "Exception: ...".

File: provider.php

<?php

require_once dirname(__FILE__) . '/vendor/autoload.php';

use GlenScott\OAuth;
use GlenScott\OAuthProvider;

require_once 'datastore.php';

$server = new OAuth\Server(new OAuthProvider\ExampleDataStore());
$server->add_signature_method(new OAuth\SignatureMethod_HMAC_SHA1());
 
$request = OAuth\Request::from_request();
 
try {
    if ( $server->verify_request($request) ) {
        echo json_encode(true);
    }
}
catch (Exception $e) {
    echo json_encode("Exception: " . $e->getMessage());
}

file: datastore.php

<?php

namespace GlenScott\OAuthProvider;

require_once dirname(__FILE__) . '/vendor/autoload.php';

use GlenScott\OAuth;

class ExampleDataStore extends OAuth\DataStore {
    function lookup_consumer($consumer_key) {
        $consumer_secrets = array( 'thisisakey'     => 'thisisasecret',
                                   'anotherkey'     => 'f3ac5b093f3eab260520d8e3049561e6',
                                 );
 
        if ( isset($consumer_secrets[$consumer_key])) {
            return new OAuth\Consumer($consumer_key, $consumer_secrets[$consumer_key], NULL);
        }
        else {
            return false;
        }
    }
 
    function lookup_token($consumer, $token_type, $token) {
        // we are not using tokens, so return empty token
        return new OAuth\Token("", "");
    }
 
    function lookup_nonce($consumer, $token, $nonce, $timestamp) {
        // @todo lookup nonce and make sure it hasn't been used before (perhaps in combination with timestamp?)
        return NULL;
    }
 
    function new_request_token($consumer, $callback = null) {
 
    }
 
    function new_access_token($token, $consumer, $verifier = null) {
 
    }
}

Consumer side

file: consumer.php

<?php

require_once dirname(__FILE__) . '/vendor/autoload.php';
 
use GlenScott\OAuth;

// this is sent with each request, and doesn't matter if it is public
$consumer_key = 'thisisakey';
 
// this should never be sent directly over the wire
$private_key  = 'thisisasecret';
 
// API endpoint -- note that in prodution, you _must_ use https rather than http
$url = 'http://localhost:8080/provider.php';
 
// the custom paramters you want to send to the endpoint
$params = array( 'foo' => 'bar',
                 'bar' => 'foo',
                 );
 
$consumer = new OAuth\Consumer($consumer_key, $private_key);
$request  = OAuth\Request::from_consumer_and_token($consumer, NULL, 'GET', $url, $params);
 
$sig = new OAuth\SignatureMethod_HMAC_SHA1();
 
$request->sign_request($sig, $consumer, null);
 
$opts = array(
    'http' => array(
        'header' => $request->to_header()
    )
);
 
$context = stream_context_create($opts);
 
$url = $url . '?' . http_build_query($params);
 
echo "Making request: " . $url . PHP_EOL;
echo "Authorization HTTP Header: " . $request->to_header() . PHP_EOL;
echo "Response: " . file_get_contents($url, false, $context) . PHP_EOL;

To run the above example, run the PHP development server using php -S localhost:8080 and run php consumer.php:

Making request: http://localhost:8080/provider.php?foo=bar&bar=foo
Authorization HTTP Header: Authorization: OAuth oauth_version="1.0",oauth_nonce="7620de430a896a5594fbf76e96f3b3d3",oauth_timestamp="1510494497",oauth_consumer_key="thisisakey",oauth_signature_method="HMAC-SHA1",oauth_signature="4V8Ft368ZBWxh5V10jv1AW%2FJwls%3D"
Response: true

Using the samples above should give you a head-start when creating your own authenticated API.

Any questions? Please use the comments section below!

Glen Scott

I’m a freelance software developer with 18 years’ professional experience in web development. I specialise in creating tailor-made, web-based systems that can help your business run like clockwork. I am the Managing Director of Yellow Square Development.

More Posts

Follow Me:
TwitterFacebookLinkedIn

4 thoughts on “Securing your PHP REST API with OAuth

  1. shyam

    Hi Glenn.
    i am trying to implement the example and it is working fine with GET but when i try to use POST method to send data i am getting “Exception: Invalid signature” response could you please tell me how can i use POST method,, Thanks a lot for writing the tutorial it is really help full..

    Reply
    1. Glen Scott Post author

      Hey,

      Here’s a quick and dirty example of how to POST data to your API. The example uses PHP’s curl functions:

      <?php
      
      // test usage of access token
      $url = 'http://example.com/foo';
      
      $params = array('foo' => 'bar',);
      
      $request  = OAuthRequest::from_consumer_and_token($consumer, NULL, 'POST', $url, $params);
      
      $sig = new OAuthSignatureMethod_HMAC_SHA1();
      
      $request->sign_request($sig, $consumer, $token);
      
      echo "Making request: " . $url . PHP_EOL;
      echo "Authorization HTTP Header: " . $request->to_header() . PHP_EOL;
      
      $ch = curl_init($url);
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
      curl_setopt($ch, CURLOPT_HTTPHEADER, array($request->to_header()));
      curl_setopt($ch, CURLOPT_HEADER, 1);
      
      $response = curl_exec($ch);
      
      Reply
  2. raj

    Hey Shyam can u help me implement this ?
    I have cloned the repo from github but don’t no how to begin.
    I am a newbie to this thing.
    Please help.

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.