APIs that give third-party applications access to user data, need to have a way to verify that the user has truly authorised the client to access the information.
There comes in the industry standard, OAuth protocol.

What is OAuth?

An open protocol to allow secure authorisation in a simple and standard method from web, mobile and desktop applications.. You've seen OAuth at work many times, especially when you want to sign up for a new service. For example, to sign up on Stackoverflow. You can either manually enter the sign up details or simply use your Google or Facebook account. Who wants to enter the same details always, surely not me.

We authorise (the core function of OAuth) clients to act on our behalf without sharing our secret credentials with the application.

For the Stackoverflow example mentioned above. If the user decides to sign up with Facebook. It will require an authorisation from the user in order to grant access to Stackoverflow. The user does not disclose the Facebook password, which can be used to access every other private data.

OAuth2.0 is the latest version of OAuth and by far the most commonly used currently. Consequently, this write-up would be based on it. I wiil henceforth simply refer to OAuth2.0 as OAuth.

What is OAuth not?

As opposed to the common misuse of OAuth, it is not an authentication system. But rather just for system authorisation across the web.

Why OAuth?

The main function of OAuth is to grant secured and limited authorisation for a third-party access to user information.
As mentioned above, this provides an easier way and better user experience in registering for services. While also protecting the user's data. Especially now that everyone is very cautious of who gets their private data. Therefore, the importance of OAuth in authorisation cannot be overemphasized.

This post may seem a bit long. That's because it has both OAuth consumption and implementing an OAuth server. The OAuth server was implemented using a library. I will still do a post on how to build your own OAuth server library.

OAuth Process Flow
To understand how OAuth works, we will follow these steps more or less.
i. Roles
ii. Creating an app
iii. Authorisation and
iv. Finally making an authenticated request.

Roles

Third-Party Application: "Client"

The client is the application that attempts to get access to the user's account. In reference to the Stackoverflow sign-up using Facebook account. Stackoverflow will be the client trying to access the username and other public information from Facebook.

The API: "Resource Server"

As stated in their technical specification. It is the server hosting the protected resources, capable of accepting and responding to protected resource requests using access tokens. Still using our example, the resource server would be the Facebook server hosting the user data.

The Authorisation Server

Authorisation server handles the denial or approval of the request to access certain information about a user. This usually comes as a dialogue box, the user can either agree or deny the request.
An example could be: “do you allow this application to have access to these scopes?” So, the user has to be authenticated by the Resource Server. In the case that the user is already signed-in he just has to grant access or deny, if not the user will have to sign in. But many times, for an application regularly used, there is already a cached session cookie. So no need to sign in. It could also be that the authorisation would demand for a sign-in first. In that case the user should do so. Then grant or deny the authorisation

The Resource Owner

This is the user that owns the information that the third-party application seeks to access. The decision to grant permission or not lies with the resource owner. Using our example, it would be the person that wants to sign up to Stackoverflow.

The simple diagram below illustrates relationship between the various roles.

alt oauth

The path of the whole process is more or less like this. The client seeks to obtain a certain scope of information from the resource server. But it first of all has to obtain a permission from the user. The user is redirected to the authorisation server where it is given an authorisation code. The user passes the authorisation code to the client who in turn uses it to obtain an access token from the authentication server. The authentication token gotten is then passed to the resource server. If it is correct, access is granted to the client for a given scope.

Creating an App

To begin the OAuth process, you must first register the app with the service. Still using the Stackoverflow example, the app is first registered on Facebook and Google. This demands the supply basic information, like the application name, website, a logo, etc. Usually a redirect URI will be required.

Redirect URIs
To prevent attacks, the service will only redirect users to a registered URI. If a redirect URI is HTTP, it should be protected with TLS. The service will only redirect to HTTPS URIs. This is to prevent the token from being intercepted during authorisation process. Nevertheless, native apps may register a redirect URI with a custom URL, which may look like myapp://redirect.

Client ID and Secret
When the app is duly registered, you will be given a client secret and a client ID. The client ID is like the public key
while the former is like your secret key. It should be kept strictly confidential. In fact if an app cannot keep the secret confidential, such as single-page JS apps or native apps, then the secret is not used. Ideally, the service shouldn't even issue a secret to these types of apps in the first place.

Authorization

The first step of OAuth is to get authorization from the user.
For browser-based or mobile apps, this is usually accomplished by displaying an interface provided by the service to the user.

OAuth provides a number of "grant types" for different use cases. Namely:

i. Authorization Code for apps running on a web server, browser-based and mobile apps
ii. Password for logging in with a username and password
iii. Client credentials for application access
iv. Implicit was previously recommended for clients without a secret, but has been superseded by using the Authorization Code grant with no secret.

We will only focus on Web Server Apps. Which will be based on the material found on https://aaronparecki.com. You may also consult the page to learn about the other three.

Web Server Apps
This is the most common type of application encountered when dealing with OAuth servers. Since web apps are written in a server-side language and run on a server (not the browser). Therefore, the source code is usually not available for public view. This means that the application can use its client secret when communicating with authorisation server.

Authorization
You have to create a "Log In" link sending the user to:

https://authorization-server.com/auth?response_type=code&client_id=CLIENT_ID&redirect_uri=REDIRECT_URI&scope=all&state=nasatess

Of course you will edit and add the corresponding details to yours.
code - Indicates that your server expects to receive an authorization code
client_id - The client ID you received when you first created the application
redirect_uri - Indicates the URI to return the user to after authorization is complete
scope - One or more scope values indicating which parts of the user's account you wish to access
state - A random string generated by your application, which you'll verify later

An authorisation prompt will be shown to the user. If the user clicks "Allow," the service redirects the user back to your site with an auth code

https://example-app.com/cb?code=AUTH_CODE_HERE&state=nasatess

  • code - The authorisation code will be returned in the query string
  • state - The string passed as state is returned here.

This should be compared with the one you started with to be sure they match. Often, this is achieved by storing it in a cookie or session. This helps to prevent the redirection endpoint from being tricked into attempting to exchange arbitrary authorisation codes.

Token Exchange
Your server exchanges the auth code for an access token:

POST https://api.authorization-server.com/token
grant_type=authorization_code&
code=AUTH_CODE_HERE&
redirect_uri=REDIRECT_URI&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET

grant_type=authorization_code - The grant type for this flow is authorization_code
code=AUTH_CODE_HERE - This is the code you received in the query string
redirect_uri=REDIRECT_URI - Must be identical to the redirect URI provided in the original link
client_id=CLIENT_ID - The client ID you received when you first created the application
client_secret=CLIENT_SECRET - Since this request is made from server-side code, the secret is included
The server replies with an access token and expiration time

{
  "access_token":"RsT5OjbzRn430zqMLgV3Ia",
  "expires_in":3600
}

or if there was an error

{
  "error":"invalid_request"
}

Security: Note that the service must require apps to pre-register their redirect URIs.

Making Authenticated Requests

The end result of all the processes is to obtain an access token.

With an access token, you can make requests to the API. This can be done using cURL or any API request tool like Postman.
Using cURL:

curl -H "Authorization: Bearer RsT5OjbzRn430zqMLgV3Ia" \
https://api.authorization-server.com/1/me

Yeah, It's done! Make sure you always send requests over HTTPS and never ignore invalid certificates. HTTPS is the only thing protecting requests from being intercepted or modified.

Implementing OAuth2 Server

This part implements an OAuth server using the oauth2-server-php library in the bshaffer repository on github. This library will handle OAuth2 protocol for us.

Follow these steps:

  1. Clone the repo from github.
    git clone http://bshaffer.github.io/oauth2-server-php

  2. Create your db.
    Create a database. Run the following queries to create your tables.

     CREATE TABLE oauth\_clients (
     client\_id             VARCHAR(80)   NOT NULL,
     client\_secret         VARCHAR(80),
     redirect\_uri          VARCHAR(2000),
     grant\_types           VARCHAR(80),
     scope                 VARCHAR(4000),
     user\_id               VARCHAR(80),
     PRIMARY KEY (client\_id)
     );
    
     CREATE TABLE oauth_access_tokens (
     access_token         VARCHAR(40)    NOT NULL,
     client_id            VARCHAR(80)    NOT NULL,
     user_id              VARCHAR(80),
     expires              TIMESTAMP      NOT NULL,
     scope                VARCHAR(4000),
     PRIMARY KEY (access_token)
     );
    
     CREATE TABLE oauth\_authorization\_codes (
     authorization\_code  VARCHAR(40)     NOT NULL,
     client\_id           VARCHAR(80)     NOT NULL,
     user\_id             VARCHAR(80),
     redirect\_uri        VARCHAR(2000),
     expires             TIMESTAMP       NOT NULL,
     scope               VARCHAR(4000),
     id_token            VARCHAR(1000),
     PRIMARY KEY (authorization_code)
     );
    
     CREATE TABLE oauth_refresh_tokens (
     refresh_token       VARCHAR(40)     NOT NULL,
     client_id           VARCHAR(80)     NOT NULL,
     user_id             VARCHAR(80),
     expires             TIMESTAMP       NOT NULL,
     scope               VARCHAR(4000),
     PRIMARY KEY (refresh_token)
     );
    
     CREATE TABLE oauth_users (
     username            VARCHAR(80),
     password            VARCHAR(80),
     first_name          VARCHAR(80),
     last_name           VARCHAR(80),
     email               VARCHAR(80),
     email_verified      BOOLEAN,
     scope               VARCHAR(4000),
     PRIMARY KEY (username)
     );
    
     CREATE TABLE oauth_scopes (
     scope               VARCHAR(80)     NOT NULL,
     is_default          BOOLEAN,
     PRIMARY KEY (scope)
     );
    
     CREATE TABLE oauth_jwt (
     client_id           VARCHAR(80)     NOT NULL,
     subject             VARCHAR(80),
     public_key          VARCHAR(2000)   NOT NULL
     );
    

An option to do that is to save the queries in an sql file. Enter the mysql shell and run the source file.

mysql> source file.sql;

  1. Create and configure an OAuth server

Create a server.php file with the following snippet.

$dsn = 
'mysql:dbname=dbName;host=localhost';
$username = 'root';
$password = '';

// error reporting 


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
$server->addGrantType(new OAuth2\GrantType\AuthorizationCode($storage));

Create a token.php file

// include our OAuth2 Server object.
require_once \_\_DIR\_\_.'/server.php';

// Handle a request for an OAuth2.0 Access Token and send the response to the client
$server->handleTokenRequest
(OAuth2\Request::createFromGlobals())->send();

Insert values into the database to see how this works.

INSERT INTO oauth_clients (client_id, client_secret, redirect_uri) VALUES ("testclient", "testpass", "http://fake/");

Run this on the terminal.

curl -u testclient:testpass http://localhost/OAuth/token.php -d 'grant_type=client_credentials'

If all works well you should receive a response like this:

{"access_token":"03807cb390319329bdf6c777d4dfae9c0d3b3c35","expires_in":3600,"token_type":"bearer","scope":null}

An access token is returned along with other particulars, like when it will expire, etc.

Create a resource.php file.

// 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;
}
echo json_encode(array('success' => true, 'message' => 'You accessed my APIs!'));

Run the following on your terminal.

curl http://localhost/resource.php -d 'access_token=YOUR_TOKEN'
  • Replace 'YOUR_TOKEN' with the token retrieved in the previous step.

In the case of success, this message will be returned:
{"success":true,"message":"You accessed my APIs!"}

Let's include the 'wow!' feature in OAuth. Which is the authorise controllers. We only dispense a token if the user has authorised it.

Create an authorise.php file

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

$request = OAuth2\Request::createFromGlobals();
$response = new OAuth2\Response();

// validate the authorize request
if (!$server->validateAuthorizeRequest($request, 
$response)) {
$response->send();
die;
}
// display an authorization form
if (empty($_POST)) {
exit('
<form method="post">
<label>Do You Authorize TestClient?</label><br />
<input type="submit" name="authorized" value="yes">
<input type="submit" name="authorized" value="no">
</form>');
}

// print the authorization code if the user has authorized your client
$is_authorized = ($_POST['authorized'] === 'yes');
$server->handleAuthorizeRequest($request, $response, $is_authorized);
if ($is_authorized) {
// this is only here so that you get to see your code in the cURL request. Otherwise, we'd redirect back to the client
$code = substr($response->getHttpHeader('Location'), strpos($response->getHttpHeader('Location'), 'code=')+5, 40);
exit("SUCCESS! Authorization Code: $code");
}
$response->send();

Paste this on the browser:
http://localhost/authorize.php?response_type=code&client_id=testclient&state=xyz
This will return a code. The code will then be used in exchange for authorisation token.

Run this on the terminal.

curl -u testclient:testpass  http://localhost/token.php -d 'grant\_type=authorization\_code&code=YOUR_CODE'

Enter the code you received at the previous step. The code expires in 30 seconds, after which it cannot be used to obtain an access token anymore.

Finally, after the last step above you will receive an access token. I very understand that some things may not very clear, feel free to ask at the comments section below.