OAuth2 on a Virtual Server

Can a $5-a-month virtual server run an OAuth2 Server? My virtual server has been my testbed for many IoT projects that make Web API calls to the web services that I have developed. None of these were as secure as I want them to be because I have never gone around implementing an OAuth2 server. Instead, I had mostly depended on POST with username and password to authenticate a user. This is not optimum and definitely not elegant. How do I even sleep at night!?

My $5-a-month virtual server runs PHP and MySQL, so I set out to find out if a PHP implementation of an OAuth2 server will allow me to authenticate my Web API calls. B. Shaffer’s OAuth2 PHP Library is what I used: https://bshaffer.github.io/oauth2-server-php-docs/, and this is a documentation of how I did it.

What I Did

Visit Shaffer’s github on https://github.com/bshaffer/oauth2-server-php/tree/v0.9 to download his source code into your virtual server. I have mine on jacksonng.org/oauth/.

Then I followed through Shaffer’s Step-By-Step Walkthrough here: https://bshaffer.github.io/oauth2-server-php-docs/cookbook/. I needed to be able to grant access to my Web API resources using a set of user/password credentials. I also wanted to be able to refresh the OAuth2 token every time it expires. Thus my server.php was modified to look like this:

<?php
$dsn      = 'mysql:dbname=;host=mydblocation';
$username = 'myusername';
$password = 'mypassword';

// error reporting (this is a demo, after all!)
ini_set('display_errors',1);error_reporting(E_ALL);

// Autoloading (composer is preferred, but for this example let's just do this)
require_once('oauth2-server-php/src/OAuth2/Autoloader.php');
OAuth2\Autoloader::register();

// $dsn is the Data Source Name for your database, for exmaple "mysql:dbname=my_oauth2_db;host=localhost"
$storage = new OAuth2\Storage\Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password));

// Pass a storage object or array of storage objects to the OAuth2 server class
$server = new OAuth2\Server($storage);

// Add the "Client Credentials" grant type (it is the simplest of the grant types)
$server->addGrantType(new OAuth2\GrantType\ClientCredentials($storage));

// Add the "Authorization Code" grant type (this is where the oauth magic happens)
$server->addGrantType(new OAuth2\GrantType\AuthorizationCode($storage));


//for user credential
$users = array('jacksonng' => array('password' => 'jackson', 'first_name' => 'jackson', 'last_name' => 'ng'));
$clients = array('TestClient' => array('client_secret' => 'TestSecret'));
$storage = new OAuth2\Storage\Memory(array('user_credentials' => $users, 'client_credentials' => $clients));
$grantType = new OAuth2\GrantType\UserCredentials($storage);
$server->addGrantType($grantType);

//for refresh of token
$storage = new OAuth2\Storage\Pdo(array('dsn' => $dsn, 'username' => $username, 'password' => $password));
$grantType = new OAuth2\GrantType\RefreshToken($storage);
$server->addGrantType($grantType);
?>

I executed curl to test if OAuth2 will give me a token if I authenticated using client credentials:

curl -u testclient:testpass http://jacksonng.org/oauth/token.php -d "grant_type=client_credentials"

It does!

And then I executed curl to test if OAuth2 will give me a token if I authenticated using user credentials:

curl http://jacksonng.org/oauth/token.php -d "grant_type=password&client_id=TestClient&client_secret=testpass&username=jacksonng&password=jackson"

It does again!

And I wanted to make sure I could receive a new token by providing a refresh token:

curl http://jacksonng.org/oauth/token.php -d "grant_type=refresh_token&refresh_token=c813ce0a8b3c0ebcb8b94ebe68af3c88eedfbf6d&client_id=TestClient&client_secret=testpass&username=jacksonng&password=jackson"

Finally, I wanted OAuth2 to grant me access to some web services in resource.php. Based on Shaffer’s example of resource.php, anyone who has a valid token will be granted access to web services here. I don’t want this to happen. Instead, I want to allow only users who have access to the scope called “resource” to access it. To do this, I first insert a new record with the field “resource” to my OAuth2 database table, oauth_scopes.

And then I request for a token and provide “resource” as a scope:

curl http://jacksonng.org/oauth/token.php -d "grant_type=password&client_id=TestClient&client_secret=testpass&username=jacksonng&password=jackson&scope=resource"

OAuth2 gives me an access token. With the access token, I attempted to access the resource:

curl http://jacksonng.org/oauth/resource.php -d "access_token=7fa69c2b671ca52ba65051b12f636ab8a26002dc"

And this is what I see:

{"success":true,"message":"You accessed my APIs!"}

Which is great, but I want OAuth2 to deny me access to the Web API services in resource.php if I didn’t have the rights to it. So I modified resource.php to this:

<?php
// include our OAuth2 Server object
require_once __DIR__.'/server.php';

// Handle a request to a resource and authenticate the access token
if (!$server->verifyResourceRequest(OAuth2\Request::createFromGlobals())) {
    $server->getResponse()->send();
    die;
}


$request = OAuth2\Request::createFromGlobals();
$response = new OAuth2\Response();
$scopeRequired = 'basic'; //note that the scope for this resource is 'basic', and my scope is 'resource'
if (!$server->verifyResourceRequest($request, $response, $scopeRequired)) {
  //if the scope required is different from what the token allows, this will send a "401 insufficient_scope" error
  $response->send();
}
else {
    echo json_encode(array('success' => true, 'message' => 'You accessed my APIs!'));
}

?>

And this is what I see now when I execute curl again. Now I can access the API.

{"error":"insufficient_scope","error_description":"The request requires higher privileges than provided by the access token"}

To change the scope of the Web services in resources.php, I change $scopeRequired to ‘resource’ and executed curl again:

{"success":true,"message":"You accessed my APIs!"}

What I learnt

In this experiment, I sought to learn a few OAuth2 techniques in securing Web APIs. I also wanted to know these:

  1. Can a virtual web hosting server with no administrator access host a OAuth2 server?
  2. Could I authenticate with username and password?
  3. How simple is it to grant access to Web API based on scope?

And I learnt that the answers to all 3 questions are positive. Next I will like to apply this new skill to several of Web Services that I have written for my IoT devices, but that’s a task for another day. 

Photo by imgix on Unsplash