Securing your API with OAuth 2.0

In a previous post I announced my new OAuth 2.0 PHP libraries.

In this post I will show you how to use the server library to secure a simple API with OAuth 2.0.


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 the storage models

In order to retrieve data from the database you should create classes which implement the following interfaces:

Hooking it all up

Setting up the library is simple, just create a new instance of \OAuth2\ResourceServer and pass in your storage models.

// Include the storage models
include 'model_scope.php';
include 'model_session.php';

// Initiate the Request handler
$request = new \OAuth2\Util\Request();

// Initiate the auth server with the models
$server = new \OAuth2\ResourceServer(new SessionModel, new ScopeModel);

Checking for valid access tokens

Before your API responds you need to check that an access token has been presented with the request (either in the query string ?access_token=abcdef or as an authorization header Authorization: bearer abcdef).

If you’re using a framework such as Laravel or CodeIgniter you could use a route filter to do this, or have a custom controller which other controllers extend from. In this example I’m using the Slim framework and I’m going to create a simple route middleware which is run before each endpoint function.

$checkToken = function () use ($server) {

    return function() use ($server)
    {
        // Test for token existance and validity
        try {
            $server->isValid();
        }

        // The access token is missing or invalid...
        catch (\OAuth2\Exception\InvalidAccessTokenException $e)
        {
            $app = \Slim\Slim::getInstance();
            $res = $app->response();
            $res['Content-Type'] = 'application/json';
            $res->status(403);

            $res->body(json_encode(array(
                'error' =>  $e->getMessage()
            )));
        }
    };

};

When $server->isValid() is called the library will run the following tasks:

Assuming an exception isn’t thrown you can then use the following functions in your API code:

A simple example

This example endpoint will return a user’s information if a valid access token is present. If the access token has the user.contact it will return additional information.

$app->get('/user/:id', $checkToken(), function ($id) use ($server, $app) {

    $user_model = new UserModel();

    $user = $user_model->getUser($id);

    if ( ! $user)
    {
        $res = $app->response();
        $res->status(404);
        $res['Content-Type'] = 'application/json';
        $res->body(json_encode(array(
            'error' => 'User not found'
        )));
    }

    else
    {
        // Basic response
        $response = array(
            'error' => null,
            'result'    =>  array(
                'user_id'   =>  $user['id'],
                'firstname' =>  $user['firstname'],
                'lastname'  =>  $user['lastname']
            )
        );

        // If the acess token has the "user.contact" access token include
        //  an email address and phone numner
        if ($server->hasScope('user.contact'))
        {
            $response['result']['email'] = $user['email'];
            $response['result']['phone'] = $user['phone'];
        }

        // Respond
        $res = $app->response();
        $res['Content-Type'] = 'application/json';

        $res->body(json_encode($response));
    }

});

Limiting an endpoint to a specific owner type

In this example, only a user’s access token is valid:

$app->get('/user', $checkToken(), function () use ($server, $app) {

    $user_model = new UserModel();

    // Check the access token's owner is a user
    if ($server->getOwnerType() === 'user')
    {
        // Get the access token owner's ID
        $userId = $server->getOwnerId();

        $user = $user_model->getUser($userId);

        // If the user can't be found return 404
        if ( ! $user)
        {
            $res = $app->response();
            $res->status(404);
            $res['Content-Type'] = 'application/json';
            $res->body(json_encode(array(
                'error' => 'Resource owner not found'
            )));
        }

        // A user has been found
        else
        {
            // Basic response
            $response = array(
                'error' => null,
                'result'    =>  array(
                    'user_id'   =>  $user['id'],
                    'firstname' =>  $user['firstname'],
                    'lastname'  =>  $user['lastname']
                )
            );

            // If the acess token has the "user.contact" access token include
            //  an email address and phone numner
            if ($server->hasScope('user.contact'))
            {
                $response['result']['email'] = $user['email'];
                $response['result']['phone'] = $user['phone'];
            }

            // Respond
            $res = $app->response();
            $res['Content-Type'] = 'application/json';

            $res->body(json_encode($response));
        }
    }

    // The access token isn't owned by a user
    else
    {
        $res = $app->response();
        $res->status(403);
        $res['Content-Type'] = 'application/json';
        $res->body(json_encode(array(
            'error' => 'Only access tokens representing users can use this endpoint'
        )));
    }

});

You might use an API function like this to allow a client to discover who a user is after they’ve signed into your authorization endpoint (see an example of how to do this here).

Limiting an endpoint to a specific owner type and scope

In this example, the endpoint will only respond to access tokens that are owner by client applications and that have the scope users.list.

$app->get('/users', $checkToken(), function () use ($server, $app) {

    $user_model = new UserModel();

    $users = $user_model->getUsers();

    // Check the access token owner is a client
    if ($server->getOwnerType() === 'client' && $server->hasScope('users.list'))
    {
        $response = array(
            'error' => null,
            'results'   =>  array()
        );

        $i = 0;
        foreach ($users as $k => $v)
        {
            // Basic details
            $response['results'][$i]['user_id'] = $v['id'];
            $response['results'][$i]['firstname'] = $v['firstname'];
            $response['results'][$i]['lastname'] = $v['lastname'];

            // Include additional details with the right scope
            if ($server->hasScope('user.contact'))
            {
                $response['results'][$i]['email'] = $v['email'];
                $response['results'][$i]['phone'] = $v['phone'];
            }

            $i++;
        }

        $res = $app->response();
        $res['Content-Type'] = 'application/json';

        $res->body(json_encode($response));
    }

    // Access token owner isn't a client or doesn't have the correct scope
    else
    {
        $res = $app->response();
        $res->status(403);
        $res['Content-Type'] = 'application/json';
        $res->body(json_encode(array(
            'error' => 'Only access tokens representing clients can use this endpoint'
        )));
    }

});

You might secure an endpoint in this way to only allow specific clients (such as your applications’ main website) access to private APIs.


Hopefully you will see how easy it is to secure an API with OAuth 2.0 and how you can use scopes to limit response contents or access to endpoints.

You can download a complete working example here - https://github.com/lncd/oauth2-example-resource-server.