Developing an OAuth 2.0 authentication server

This guide will show you how to setup an OAuth 2.0 authentication server which supports the authorization code grant type.

Install the library

The recommended way of installing the library is via Composer.

If you already have a composer.json file in your root then add ”lncd/oauth2”: “*” in the require object. Then run composer update.

Otherwise create a new file in your project root called composer.json add set the contents to:

{
    "require": {
        "lncd\OAuth2": "*"
    }
}

Now, assuming you have installed Composer run composer install.

Ensure now that you’ve set up your project to autoload composer packages.


You could alternatively add the library as a git submodule or download a zip.

Set up the database

To setup the database just import sql/mysql.sql

Create your first client

In OAuth terms a client is an application (it could be a website or a mobile app) that communicates with your API.

Insert a client into the oauth_clients table. It is recommended that you make the id and secret fields random alphanumeric strings – http://randomkeygen.com/ is a useful for this. The auto_approve parameter should be to 1 if you want the user to automatically approve access to the client, otherwise set it to 0.

If you want to use the authorization grant (where a user is redirected to the auth server from the client and the back in order to “sign-in” or “connect” with the client) then in the oauth_client_endpoints add a redirect URI (where the user is redirected back to after signing in). You can add multiple redirect URIs for production and development.

Create the storage models

In order to persist data to the database you should create classes which implement the following three interfaces:

  • \OAuth2\Storage\ClientInterface
  • \OAuth2\Storage\ScopeInterface
  • \OAuth2\Storage\SessionInterface

The authorization code grant

For reference here is an overview of how the authorization code grant works:

+--------+                               +---------------+
|        |--(A)- authorisation Request ->|   Resource    |
|        |                               |     Owner     |
|        |< -(B)-- authorisation Grant ---|               |
|        |                               +---------------+
|        |
|        |                               +---------------+
|        |--(C)-- authorisation Grant -->| authorisation |
| Client |                               |     Server    |
|        |< -(D)----- Access Token -------|               |
|        |                               +---------------+
|        |
|        |                               +---------------+
|        |--(E)----- Access Token ------>|    Resource   |
|        |                               |     Server    |
|        |< -(F)--- Protected Resource ---|               |
+--------+                               +---------------+

(A) The client requests authorisation from the resource owner. The authorisation request can be made directly to the resource owner (as shown), or preferably indirectly via the authorisation server as an intermediary.

(B) The client receives an authorisation grant, which is a credential representing the resource owner’s authorisation, expressed using one of four grant types defined in this specification or using an extension grant type. The authorisation grant type depends on the method used by the client to request authorisation and the types supported by the authorisation server.

(C) The client requests an access token by authenticating with the authorisation server and presenting the authorisation grant.

(D) The authorisation server authenticates the client and validates the authorisation grant, and if valid issues an access token.

(E) The client requests the protected resource from the resource server and authenticates by presenting the access token.

(F) The resource server validates the access token, and if valid, serves the request.

Create an oauth controller

NOTE: This is assuming you’re using a framework that follows an MVC pattern, If you’re using individual files for each page then you create a new page for each controller route listed henceforth.

In your controller constuctor you should instantiate the auth server:

public function __construct()
{
    // Initiate the request handler which deals with $_GET, $_POST, etc
    $request = new \OAuth2\Util\Request();

    // Create the auth server, the three parameters passed are references to the storage models
    $this->authserver = new \OAuth2\AuthServer(new ClientModel, new SessionModel, new ScopeModel);

    // Enable the authorization code grant type
    $this->authserver->addGrantType(new \OAuth2\Grant\AuthCode());

    // Set the TTL of an access token in seconds (default to 3600s / 1 hour)
    $this->authserver->setExpiresIn(86400);
}

Create your first route (for example “index” – which would resolve to /oauth).

public function action_index()
{
    try {

        // Tell the auth server to check the required parameters are in the query string
        $params = $this->authserver->checkAuthoriseParams();

        // Save the verified parameters to the user's session
        Session::put('client_id', $params['client_id']);
        Session::put('client_details', $params['client_details']);
        Session::put('redirect_uri', $params['redirect_uri']);
        Session::put('response_type', $params['response_type']);
        Session::put('scopes', $params['scopes']);

        // Redirect the user to the sign-in route
        return Redirect::to(‘oauth/signin');

    } catch (Oauth2\Exception\ClientException $e) {

        // Throw an error here which says what the problem is with the auth params

    } catch (Exception $e) {

        // Throw an error here which has caught a non-library specific error

    }
}

Next create a sign-in route:

public function action_signin()
{
    // Retrieve the auth params from the user's session
    $params['client_id'] = Session::get('client_id');
    $params['client_details'] = Session::get('client_details');
    $params['redirect_uri'] = Session::get('redirect_uri');
    $params['response_type'] = Session::get('response_type');
    $params['scopes'] = Session::get('scopes');

    // Check that the auth params are all present
    foreach ($params as $key=>$value) {
        if ($value === null) {
            // Throw an error because an auth param is missing - don't continue any further
        }
    }

    // Process the sign-in form submission
    if (Input::get('signin') !== null) {
        try {

            // Get username
            $u = Input::get('username');
            if ($u === null || trim($u) === '') {
                throw new Exception('please enter your username.');
            }

            // Get password
            $p = Input::get('password');
            if ($p === null || trim($p) === '') {
                throw new Exception('please enter your password.');
            }

            // Verify the user's username and password
            // Set the user's ID to a session

        } catch (Exception $e) {
            $params['error_message'] = $e->getMessage();
        }
    }

    // Get the user's ID from their session
    $params['user_id'] = Session::get('user_id');

    // User is signed in
    if ($params['user_id'] !== null) {

        // Redirect the user to /oauth/authorise route
        return Redirect::to('oauth/authorise');

    }

    // User is not signed in, show the sign-in form
    else {
        return View::make('oauth.signin', $params);
    }
}

In the sign-in form HTML page you should tell the user the name of the client that their signing into.

Once the user has signed in (if they didn’t already have an existing session) then they should be redirected the authorise route where the user explicitly gives permission for the client to act on their behalf.

public function action_authorise()
{
    // Retrieve the auth params from the user's session
    $params['client_id'] = Session::get('client_id');
    $params['client_details'] = Session::get('client_details');
    $params['redirect_uri'] = Session::get('redirect_uri');
    $params['response_type'] = Session::get('response_type');
    $params['scopes'] = Session::get('scopes');

    // Check that the auth params are all present
    foreach ($params as $key=>$value) {
        if ($value === null) {
            // Throw an error because an auth param is missing - don't continue any further
        }
    }

    // Get the user ID
    $params['user_id'] = Session::get('user_id');

    // User is not signed in so redirect them to the sign-in route (/oauth/signin)
    if ($params['user_id'] === null) {
        return Redirect::to('signin');
    }

    // Check if the client should be automatically approved
    $autoApprove = ($params['client_details']['auto_approve'] === '1') ? true : false;

    // Process the authorise request if the user's has clicked 'approve' or the client
    if (Input::get('approve') !== null || $autoApprove === true) {

        // Generate an authorization code
        $code = $this->authserver->newAuthoriseRequest('user', $params['user_id'], $params);

        // Redirect the user back to the client with an authorization code
        return Redirect::to(\OAuth2\Util\RedirectUri::make($params['redirect_uri'], array(
            'code'  =>  $code,
            'state' =>  isset($params['state']) ? $params['state'] : ''
        )));
    }

    // If the user has denied the client so redirect them back without an authorization code
    if (Input::get('deny') !== null) {
        return Redirect::to(\OAuth2\Util\RedirectUri::make($params['redirect_uri'], array(
            'error' =>  $this->authserver->exceptionCodes[2],
            'error_message' =>  $this->authserver->errors[$this->authserver->exceptionCodes[2]],
            'state' =>  isset($params['state']) ? $params['state'] : ''
        )));
    }

    // The client shouldn't automatically be approved and the user hasn't yet approved it so show them a form
    return View::make('oauth.authorise', $params);
}

In the authorize form the user should again be told the name of the client and also list all the scopes (permissions) it is requesting.

The final route to create is where the client exchanges the authorization code for an access token.

public function action_access_token()
{
    try {

        // Tell the auth server to issue an access token
        $response = $this->authserver->issueAccessToken();

    } catch (\Oauth2\Exception\ClientException $e) {

        // Throw an exception because there was a problem with a the client's request
        $response = array(
            'error' =>  $this->authserver::getExceptionType($e->getCode()),
            'error_description' => $e->getMessage()
        );

    } catch (Exception $e) {

        // Throw an error when a non-library specific exception has been thrown
        $response = array(
            'error' =>  'undefined_error',
            'error_description' => $e->getMessage()
        );
    }

    header('Content-type: application/json');
    echo json_encode($response);
}

A complete example of an authorisation server can be found at https://github.com/lncd/oauth2-example-auth-server.

PHP, meet OAuth 2.0

Over the last few months I’ve been developing a PHP library that helps you work with OAuth 2.0 in a number of different ways:

  • Develop an authentication server which can be used as part of a web “single sign on” solution.
  • Secure your APIs with access tokens and scopes.
  • Easily sign users in to many different OAuth 2.0 identity providers.

The code for the authentication and resource server can be found on Github here https://github.com/lncd/OAuth2.

The server library code requires PHP 5.3+, is hooked into Packagist (a bit like Ruby Gems but for PHP) and has 100% unit test code coverage. It has built in support for the following grants:

You can easily create you own grants by extending \OAuth2\Grant\GrantInterface. I’m going to be creating plugins which support JSON web tokens and the SAML assertions.

The code for the client library can be found here https://github.com/lncd/OAuth2-Client – at the time of writing it isn’t quite finished, I’ll blog when it is.

Over the next few blog posts I’ll document how to use the libraries.

Quick update

I’ve not blogged in a little while so I thought I’d give an update on what is happening with Linkey.

First I’ve been working with Paul in the library to capture (videos and screenshots) examples of poor user experience accessing various resources including electronic journals and databases, printers, and users’ library accounts.

I’ve also been trawling through last year’s NSS results to find examples of where students particularly struggled to access IT and library resources. I will then produce visualisations of these.

It is important to capture these examples so that hopefully by the end of the project we can show how we’ve improved the situation.

This week we launched our Ezproxy service [note: requires authentication] which has already improved accessing a number of resources including articles from the American Chemical Society, EBSCO Publishing and Oxford University Press.

I’ve also been working away at the new PHP OAuth library. Originally this was just going to be code for implementing an authentication server and a resource server, but now it is also going to include client code too thanks to Phil Sturgeon offering to integrate his existing code. As a result we’re going to have a lean and mean library that can help anyone work with any aspect of OAuth 2.

How OAuth 2.0 works

The following sequence describes how the OAuth 2.0 web-server flow works. The web-server flow is the most common OAuth flow which most implementations support.

Before we begin there is some terminology that needs to be understood:

  • Resource owner (a.k.a. the User)
    An entity capable of granting access to a protected resource. When the resource owner is a person, it is referred to as an end-user.
  • Resource server
    The server hosting the protected resources, capable of accepting and responding to protected resource requests using access tokens.
  • Client
    An application making protected resource requests on behalf of the resource owner and with its authorisation. The term client does not imply any particular implementation characteristics (e.g. whether the application executes on a server, a desktop, or other devices).
  • Authorisation server
    The server issuing access tokens to the client after successfully authenticating the resource owner and obtaining authorisation.

The web-server flow

1) The client redirects the user to the authentication server with the following information

  • The client’s ID (client_id)
  • A redirect URI where the user will returned to after they’ve authenticated with the authentication server and approved the client (redirect_uri)
  • The type of response that the client accepts (response_type)
  • A list of data which the client wishes to access on behalf of the user (scope)
  • A state parameter which is an anti-CSRF token (state)
	HTTP/1.1 302 Found
	Location: https://auth-server.tld/authorise?response_type=code
	&client_id=6BZF3wT52E7PaIlF1luRrKM4LMfkl649
	&redirect_uri=http://client.tld/signin/redirect
	&scope=basic,contacts
	&state=84Y632oM3j

2) The user signs into the authentication server and, if they haven’t already, approves the client to access their data. The user is informed which data the client wishes to access (defined by the scope parameter). If the user has already approved the application the server will check if the scope parameter matches that of the previous request and if it doesn’t will ask the user to sign in again.

3) The authentication server redirects the user back to the client (based on the redirect_uri parameter) with an authorisation code and the anti-CSRF token in the query string

	HTTP/1.1 302 Found Location: http://client.tld/signin/redirect
	?code=Al0wQaeTYsji97oRWk2l6Y940wdca3J2
	&state=84Y632oM3j

4) The client then exchanges the authorisation code for an access token by authenticating itself with the authentication server

	POST http://auth-server.tld/access_token HTTP/1.1
	Content-Type: application/x-www-form-urlencoded
	client_id=6BZF3wT52E7PaIlF1luRrKM4LMfkl649
	&client_secret=Al0wQaeTYsji97oRWk2l6Y940wdca3J2
	&redirect_uri=http://client.tld/signin/redirect
	&code=Al0wQaeTYsji97oRWk2l6Y940wdca3J2
	&grant_type=authorization_code

5) The authorisation server will perform the following validation:

  • Check that the client_id and client_secret are valid parameters
  • Check that the [authorisation] code matches the client_id and the redirect_uri

If the parameter pass the validation checks then the authentication server will respond with the access token that the client needs to access the user’s data. The server may also include a refresh token in the response.

	HTTP/1.1 200 OK
	Content-Type: application/json;charset=UTF-8
	Cache-Control: no-store
	Pragma: no-cache
	
	{
		"access_token":"2YotnFZFEjr1zCsicMWpAA",
		"expires_in":3600,
		"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
	}

Accessing resources

Once the client has the access token it can then make requests to the resource server for the user’s data

	POST http://resource-server.tld/user/details HTTP/1.1
	Content-Type: application/x-www-form-urlencoded
	
	access_token=2YotnFZFEjr1zCsicMWpAA

Angst against OAuth

Tim Bray, a developer at Google wrote in a recent blog post that he believes:

The new technology coming down the pipe, OAuth 2 and friends, is way too hard for developers; there need to be better tools and services if we’re going to make this whole Internet thing smoother and safer.

As someone who has been working with OAuth 2.0 for almost 18 months now I disagree with Tim’s position that OAuth 2.0 is “way too hard” for developers. The web-server flow essentially is a simple two step process:

  1. A redirection from the client to the authorisation server
  2. A POST request to the authorisation server to swap the authorisation code for an access token

I find it hard to believe that those two simple steps are too much for developers to implement in their applications.

It’s true that OAuth 1.0a was a pain to implement – just take a look at Twitter’s OAuth 1.0a guide to see how complicated it is in compared to the v2.0 flow I’ve described above – however as more and more service providers offer OAuth 2.0 endpoints I believe that developers will realise how easy it is to actually implement.