Creating a PHP OAuth Server

Tweet

If you’ve ever integrated with another API that requires security (such as Twitter), you’ve probably consumed an OAuth service. In this article, I’ll explore what it takes to create your own three-legged OAuth server allowing you, for example, to create your own secure API which you can release publicly.

When dealing with OAuth, you will typically see it implemented as a two-legged or three-legged OAuth server. The major difference between them is that two-legged authentication doesn’t involve another user. For example, if you want to access a specific user’s Twitter information, you would consume the three-legged server because an access token must be generated for the user in your application, versus just Twitter providing you a token. We’ll focus on the three-legged variety since it’s more practical for real-world use.

We’ll use oauth-php to perform a lot of the heavy lifting for us. The library is hosted on Google Code and is not listed in Packagist, but it can still be installed using Composer. For details, check out the composer.json file in code that accompanies this article available on GitHub.

Understanding the Flow

When using a three-legged OAuth server, the typical flow that a developer would take to implement and consume the service is as follows:

The above image, courtesy of OAuth.net, is quite complex, but in simple terms it shows the following:

  • The consumer requests a token from the server
  • The consumer then directs the user to a login page, passing the token with them
  • The user logs in and is redirected back to the consumer with an access token
  • The consumer takes the access token and requests the OAuth token to use with future secure requests
  • The OAuth token is retrieved, and the developer can now make secure requests by passing the token for validation

Setting up the Database

With the oauth-php library in an accessible location, a new database needs to be created and initialized. I’ll be using the schema script found in library/store/mysql/mysql.sql.

If you browse through the tables, you’ll see that the oauth_server_registry table contains a field called osr_usa_id_ref. This is populated during the registration process by the OAuth server. It assumes you have an already existing users table that it will be related to. If you do, that’s perfect! But if not, then here is some basic SQL to create a standard user table:

CREATE TABLE users (
    id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL DEFAULT '',
    password VARCHAR(255) NOT NULL DEFAULT '',
    email VARCHAR(255) NOT NULL DEFAULT '', 
    created DATE NOT NULL DEFAULT '0000-00-00',

    PRIMARY KEY (id)
);

Creating the OAuth Server

Let’s start writing the OAuth server. The following is common to the rest of our code, so I’ve placed it in the separate file include/common.php:

<?php
require_once '../vendor/autoload.php';

session_start();

// Add a header indicating this is an OAuth server
header('X-XRDS-Location: http://' . $_SERVER['SERVER_NAME'] .
     '/services.xrds.php');

// Connect to database
$db = new PDO('mysql:host=localhost;dbname=oauth', 'dbuser', 'dbpassword');

// Create a new instance of OAuthStore and OAuthServer
$store = OAuthStore::instance('PDO', array('conn' => $db));
$server = new OAuthServer();

The file adds an additional HTTP header to each request to inform clients that this is an OAuth server. Notice that it references services.xrds.php; this file is provided with the example that comes with the oauth-php library. You should copy it from example/server/www/services.xrds.php to the root public directory of the web server.

The next few lines of code establish a connection to the database (the connection information should be updated accordingly to your own set up) and creates new instances of OAuthStore and OAuthServer objects provided by the library.

The set up for the OAuth server is now complete and the server is ready to be fully implemented. In the remaining examples, the includes/common.php file must be included each time to instantiate the server.

Allowing Registration

Before developers can consume your OAuth server, they must register themselves with it. To allow this, we need to create a basic registration form. The following fields are required because they are passed to the library: requester_name and requester_email. The remaining fields are optional: application_uri and callback_uri.

<form method="post" action="register.php">
 <fieldset>
  <legend>Register</legend>
  <div>
   <label for="requester_name">Name</label>
   <input type="text" id="requester_name" name="requester_name">
  </div>
  <div>
   <label for="requester_email">Email</label>
   <input type="text" id="requester_email" name="requester_email">
  </div>
  <div>
   <label for="application_uri">URI</label>
   <input type="text" id="application_uri" name="application_uri">
  </div>
  <div>
   <label for="callback_uri">Callback URI</label>
   <input type="text" id="callback_uri" name="callback_uri">
  </div>
 </fieldset>
 <input type="submit" value="Register">
</form>

As I mentioned earlier, the library assumes you have existing users who want to consume your server. In the following code, I create a new user in the users table, then retrieve the ID, and then pass it to the updateConsumer() method creating (or updating) the consumer key and secret for this user. When you integrate this into your application, this piece should be modified and placed under your existing login process where you already know who the user is who is registering for access.

<?php
$stmt = $db->prepare('INSERT INTO users (name, email, created) ' .
    'VALUES (:name, :email, NOW())');
$stmt->execute(array(
    'name' => $_POST['requester_name'],
    'email' => $_POST['requester_email']
));
$id = $db->lastInsertId();

$key = $store->updateConsumer($_POST, $id, true);
$c = $store->getConsumer($key, $id);
?>
<p><strong>Save these values!</strong></p>
<p>Consumer key: <strong><?=$c['consumer_key']; ?></strong></p>
<p>Consumer secret: <strong><?=$c['consumer_secret']; ?></strong></p>

On completion of the registration, the user’s new consumer key and consumer secret key are outputted. These values should be saved by the user for future use.

Now that a user is registered, they can begin making requests for an access token!

Generating a Request Token

Once a user has registered, they should perform an OAuth request to your request_token.php file. This file (once again because of the library) is extremely simple:

<?php
require_once 'include/oauth.php';

$server->requestToken();

The requestToken() method takes care of validating that the user has provided a valid consumer key and signature. If the request is valid, a new request token is returned.

Exchanging the Request Token for an Access Token

The user should be redirected to your login page once a request token has been generated. This page should expect the following URL parameters: oauth_token and oauth_callback.

The login page should retrieve the user from the users table. Once retrieved, the user ID is passed (along with the oauth_token) to the authorizeVerify() method provided by the library. Assuming the user has authorized the application, the ID of the logged in user is then associated with the consumer’s key allowing them secure access to this user’s data.
The necessary logic of a basic login.php might look like the following:

<?php
// check if the login information is valid and get the user's ID
$sql = 'SELECT id FROM users WHERE email = :email';
$stmt = $db->prepare($sql);
$result = $stmt->exec(array(
    'email' => $_POST['requester_email']
));
$row = $result->fetch(PDO::FETCH_ASSOC);

if (!$row) {
    // incorrect login
}
$id = $row['id'];
$result->closeCursor();

// Check if there is a valid request token in the current request.
// This returns an array with the consumer key, consumer secret,
// token, token secret, and token type.
$rs = $server->authorizeVerify();
// See if the user clicked the 'allow' submit button (or whatever
// you choose)
$authorized = array_key_exists('allow', $_POST);
// Set the request token to be authorized or not authorized
// When there was a oauth_callback then this will redirect to
// the consumer
$server->authorizeFinish($authorized, $id);

After the user logs in, they will be redirected back to the consuming developer’s website (via the oauth_callback parameter) with a valid token. This token and verify key can then be used in the exchange for a valid access token.

A basic access_token.php file looks like this:

<?php
require_once 'include/oauth.php';

$server->accessToken();

This file is as simple as the previously created request_token.php. The work is all done inside the accessToken() method provided by the oauth-php library. Upon a successful request, a valid oauth_token and oauth_token_secret are outputted that should be stored and used with future requests to your API.

Validating a Request

At this point, the OAuth server is up and running. But we still need to verify that a request contains a valid OAuth signature. I’ve created a basic test file that does just that:

<?php
require_once 'includes/oauth.php';

if (OAuthRequestVerifier::requestIsSigned()) {
    try {
        $req = new OAuthRequestVerifier();
        $id = $req->verify();
        // If we have a user ID, then login as that user (for
        // this request)
        if ($id) {
            echo 'Hello ' . $id;
        }
    }  catch (OAuthException $e)  {
        // The request was signed, but failed verification
        header('HTTP/1.1 401 Unauthorized');
        header('WWW-Authenticate: OAuth realm=""');
        header('Content-Type: text/plain; charset=utf8');
        echo $e->getMessage();
        exit();
    }
}

In the example, if the request is verified, I simply echo the user id of the user who logged in. I would suggest creating a re-usable method that contains this code for any API calls that require security.

Testing the OAuth Server

Finally, it’s time to test the OAuth server. Below is a simple test file that performs the above steps to require a user to login and performs a secure request:

<?php
define('OAUTH_HOST', 'http://' . $_SERVER['SERVER_NAME']);
$id = 1;

// Init the OAuthStore
$options = array(
    'consumer_key' => '<MYCONSUMERKEY>',
    'consumer_secret' => '<MYCONSUMERSECRET>',
    'server_uri' => OAUTH_HOST,
    'request_token_uri' => OAUTH_HOST . '/request_token.php',
    'authorize_uri' => OAUTH_HOST . '/login.php',
    'access_token_uri' => OAUTH_HOST . '/access_token.php'
);
OAuthStore::instance('Session', $options);

if (empty($_GET['oauth_token'])) {
    // get a request token
    $tokenResultParams = OauthRequester::requestRequestToken($options['consumer_key'], $id);

    header('Location: ' . $options['authorize_uri'] .
        '?oauth_token=' . $tokenResultParams['token'] . 
        '&oauth_callback=' . urlencode('http://' .
            $_SERVER['SERVER_NAME'] . $_SERVER['PHP_SELF']));
}
else {
    // get an access token
    $oauthToken = $_GET['oauth_token'];
    $tokenResultParams = $_GET;
    OAuthRequester::requestAccessToken($options['consumer_key'],
        $tokenResultParams['oauth_token'], $id, 'POST', $_GET);
    $request = new OAuthRequester(OAUTH_HOST . '/test_request.php',
        'GET', $tokenResultParams);
    $result = $request->doRequest(0);
    if ($result['code'] == 200) {
        var_dump($result['body']);
    }
    else {
        echo 'Error';
    }
}

OAuth requires timestamps and signatures to be appended to each request, and once again this library will perform this for us.

The first part of the above code is configuration information that should be updated accordingly to match your needs. The user ID, consumer key, and consumer secret key are all generated during the registration process on the server.

As described during the introduction to a three-legged OAuth server, the following process is performed in the above test file:

  • Ask for a request token (via the request_token.php file) with the consumer key
  • Upon receiving the token, redirect the user to the login page passing the token and callback URL via URL parameters
  • Once the user is logged in, they are redirected back to the above test page. The test page takes the token and asks for an access token (via the access_token.php file)
  • Upon success, the necessary OAuth information is returned and the test file performs a secure request to test_request.php.
  • If all goes well a basic “Hello 1” will be displayed.

Summary

At this point, you should know how to create a basic OAuth server. Using the test_request.php file as example, you can begin creating more features that are secured with Oauth! If you’d like to play around with some code, full source code for this article is available on GitHub.

Image via Fotolia

And if you enjoyed reading this post, you’ll love Learnable; the place to learn fresh skills and techniques from the masters. Members get instant access to all of SitePoint’s ebooks and interactive online courses, like Jump Start PHP.

Comments on this article are closed. Have a question about PHP? Why not ask it on our forums?

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • pedro

    Hi ,
    Where can I get the /vendor/autoload.php file ?
    thanks.

  • Karl

    This is great, thanks for posting it.
    Any chance of posting something similar for setting up an OAuth2 server, since this is for OAuth 1.0a?

  • http://www.craftitonline.com cordoval

    yes nice blog post, i hope we can see now an oauth 2 explanation blog post of the same kind. There are frameworks that already implement bundles like symfony2 fos oauth rest server bundle but there is little info on how things work.

  • W

    How to manage revoke access? Just delete the token from the database?

  • Francesco

    Hi, When I use the command “php composer.phar install”, I get this
    error :
    —-
    Loading composer repositories with package information
    Installing dependencies
    – Installing oauth-php/oauth-php (157)
    Checking out trunk/
    [RuntimeException]
    Package could not be downloaded, sh: svn: command not found

    thanks

    • http://zaemis.blogspot.com Timothy Boronczyk

      The code for oauth-php is hosted on Google Code, which uses SVN. Thus, you need to have SVN installed on your system for Composer to be able to retrieve the library.

      • Francesco

        Hi,
        Thank you !
        But, I have a problem with svn in my server, I can not install it. Are you there another solution to be able to install oauth-php/oauth-php ?
        Thanks

        • http://zaemis.blogspot.com Timothy Boronczyk

          If you’re not able to install SVN then you’ll need to download the library from the project’s website.

          • Francesco

            Thank you, I have an other question, where do I install the oauth library ? And how I have to call classes without using Composer (/vendor/autoload.php). Sorry for my english, I am a french student !!

  • Prati

    Thanks for this detailed tutorial. When I try to register a new user, the user is created successfully in the database. But it does not output key and the secret, basically the screen is blank. I tried debugging and seems like the problem is either with store or oauth server instantiation. Any clue how to get around the problem?

    • Prati

      It was due to the typo on line 18:
      $tokenResultParams = OauthRequester::requestRequestToken($options['consumer_key'], $id);
      OAuthRequester instead of OauthRequester.
      Now I am able to reach upto the login form, but when it tries to execute $server->authorizeVerify() it gets stuck. The problem seems that it is not able to retrieve oauth_token. Any suggestion?

  • http://www.software.co.il oauth2-php buggy?

    Prati
    The code is buggy and old (last update in 2010) and despite claims to the contrary does not properly support Postgres. Personally – I’m not sure this code is worth using. After fixing 2 bugs – I’m now seeing that it doesn’t correctly register the token in the access_tokens table (which is probably your problem)

    Danny

  • http://www.software.co.il Does this code work?

    Considering the age and bugginess of the code – I’d be curious to hear if someone actually ran the code in the blog post)

  • Ashish Chaturvedi

    Getting Problem in http://localhost/oauth/public/request_token.php

    ” OAuth Verification Failed: Can’t verify request, missing oauth_consumer_key or oauth_token ”

    Not Gettign what i do on this stage.