Wow! It’s been a long since last I posted here. I’ve been really busy these days. Our startup got accepted at Tetuan Valley accelerator, so I have immersed myself in the program with the rest of the team. However, recently I was asked to do a workshop at the Google Campus Madrid about becoming an OAuth provider, because it was a common problem for many startups here. Having worked for years with OAuth, I volunteered to make a workshop, and I thought that it would be nice to share it also here, so hands on!

Our scenario

We are targeting a very common situation when you have an App, new product or Saas platform, with a REST API to serve data and respond to clients requests. You want to be able to offer authorization to 3rd party applications and services, so they can access the information of your REST API in a controlled way, and also allow them to implement single-sign on throughout your OAuth-enabled authorization mechanism.

As the goal of this tutorial is that you understand the whole process of OAuth authorization and learn how to implement it from scratch, we will use the most simple scenario of a PHP server with a MySQL database, and we will not rely on any 3rd party modules, libraries or extensions.

In this tutorial we will be implementing the de facto standard OAuth flow for single-sign on, the “Server side web application flow”. More on OAuth and flows here.

Requisites

We will need a LAMP/MAMP/WAMP environment in our localhost computer. We will be using an Apache server with PHP >= 5.4, and a MySQL database. I suggest you to install PHPMyAdmin for easier database management.

We will need to set two fake virtual domains, one for our REST backend, that will be our OAuth provider, and another one for a fake 3rd party app that would access our services via OAuth. We will call them myoauthprovider.com and customapp.com. In order to do this, we need to edit our /etc/hosts file (or windows equivalent) and point both domains to localhost:

127.0.0.1 myoauthprovider.com
127.0.0.1 customapp.com

We will also define two Apache virtual hosts, one for each domain. As we will be working on several versions, and we will use a symbolic link to the last one called “current”, we will point both virtual domains to “current”. Thus, we will create two directories in our Apache htdocs directory called oauth for our OAuth provider and customapp for our 3rd party app. The ideal setup is having a “v1” directory in both, with a symbolic link called “current” pointing to “v1”. Later, when developing further versions (v2, v3, etc) we can just change the symbolic link. However, you can just put everything inside “current” and use just that directory.

# OAuth Provider vhost.
<VirtualHost *:8080>
   ServerName myoauthprovider.com
   ServerAlias api.myoauthprovider.com www.myoauthprovider.com
   ServerAdmin webmaster@localhost
   DocumentRoot /Applications/MAMPStack/apache2/htdocs/oauth/current/
   <Directory /Applications/MAMPStack/apache2/htdocs/oauth/current/>
      Options Indexes FollowSymLinks MultiViews
      AllowOverride All
      Order allow,deny
      allow from all
   </Directory>
</VirtualHost>
# Custom 3rd party app vhost.
<VirtualHost *:8080>
   ServerName customapp.com
   ServerAlias api.customapp.com www.customapp.com
   ServerAdmin webmaster@localhost
   DocumentRoot /Applications/MAMPStack/apache2/htdocs/oauth/current/
   <Directory /Applications/MAMPStack/apache2/htdocs/oauth/current/>
      Options Indexes FollowSymLinks MultiViews
      AllowOverride All
      Order allow,deny
      allow from all
   </Directory>
</VirtualHost>

Get the code

For your convenience, I have set a github repository with all the code, with separate folders for every version, so you can follow along. Get it here.

Let’s start!

Version 1

Our initial version has, as our REST backend (the one we want to turn into an OAuth provider) a simple PHP REST server using the Slim framework. There is a login page (login.php), logout (logout.php) and a home page (index.php), where you can see the user profile information as stored in our backend database. The session data is fairly simple, just the user_id, name, email and api_key.

You can import the initial database database_v1.sql. It contains some users you can use for your tests. All of them have “cambiame” as password. So you can login with credentials “john.smith@gmail.com” and password “cambiame”. The database access is managed by the DbHandler.php class (in /api/include), and assumes that your database name, user name for that database and password are all “oauth”. You can change this in DbHandler.php to your liking.

For login in, we have the endpoint /login. For simplicity’s sake, it returns just a simple api_key that allows further access to the REST API. The access points for the database are:

Access point
METHOD
Description
/register
POST
Registers a new user.
/login
POST
Login access point.
/me
GET
Returns the user profile information.

I have chosen Slim because it’s simple and easy to use, and will allow us to show the process of adding our OAuth functionality step by step. Slim is located under api, and you can define a route like this:

 

/** Profile info of the user endpoint */
$app->get('/me', function ($request, $response, $args) {
   global $userData;
   $result = array(
     "id" => $userData["id"],
     "name" => $userData["name"],
     "email" => $userData["email"],
     "api_key" => $userData["api_key"]);
   $data = array("error" => false, "user" => $result);
   return echoResponse($response, $data);
})->add($authenticate);

In this example, we are defining a GET endpoint called “/me”. The function, “->add($authenticate);” is an authentication middleware that checks the presence of a valid api_key.

For accessing the database, we will use the wonderful MySQLiDb library created by Josh Campbell. It allows you to easily perform operations in a MySQL database, like getting the info of a user:

$this->db->where("email", $email); 
if ($userData = $this->db->getOne("users")) {
   return $userData;
} else { return false; }

In the initial version of customapp.com, we just have a simple page with a “social login” button. It does nothing currently, but our goal is being able to use this button to login into customapp using our OAuth provider’s authentication.

Before continuing, make sure you can see the two pages properly, myoauthprovider.com with a blue background, and customapp.com with a violet background. The virtual hosts and /etc/hosts redirections should work as expected.

Version 2

serverSideWebApplicationFlow

The OAuth flow that we will be using for our OAuth authentication is the “Server side web application flow”. We have our OAuth provider (myoauthprovider.com) and we want to authorize our external application (customapp.com) to access some information of our customers by means of our REST backend. We will need to provide customapp.com with the needed access_token and refresh_token to allow it to perform requests to our backend in behalf of the user. You can read about the OAuth flows here, but basically the process depicted in the picture is the following:

  1. When the 3rd party application needs to access the information stored in the OAuth provider resource servers, it will present a “Social Login” or “Access with …” button. When clicked, it will be redirected to the OAuth provider server.
  2. The user is presented with your typical “3rd party App wants to access your … data, being able to read your email, name, friends information, credit card numbers, write posts on your behalf, and sell all your properties without your consent…”. This request will include the client ID of the 3rd party app (that identifies it in the OAuth provider), and a redirection URI where the OAuth provider will redirect the browser with the authorization code if the user happens to authorize the request.
  3. If the user authorizes 3rd party app to access the data, the OAuth provider redirects to the 3rd party backend/application, including the authorization code. Up to this point, the interaction has been performed via the user browser and involving the user interaction. From this point on, the interaction is just between the 3rd party backend/app and the OAuth provider, without going through the user browser. This is important to remember, as it implies exchanging the client secret (that should be kept private) of the 3rd party application between it and the OAuth provider privately.
  4. The 3rd party backend/application sends the retrieved authorization code with the application credentials (client ID and client secret), and optionally some options like type of token to get, expiration requirements, permission scope, etc…
  5. The OAuth provider checks the validity of the authorization code regarding the requesting app and the permission scope and other parameters, and issue an access_token and a refresh token. From now on, the 3rd party application/backend can use the access token to get the information until it expires, and then use the refresh token to get a new access token.

Database

We will expand our data model first. Apart from the users table, we will need a table to store three types of tokens:

  • code“: a code token will allow the custom application exchange it for an access token and a refresh token if the user authorizes the request.
  • access“: data access token. It’s a time-limited token (10 minutes) that will allow us to access some kind of data in our backend.
  • refresh“: the refresh token is a long lived token (1 year) that will allow us to get a new access token when the previous one expires.
CREATE TABLE IF NOT EXISTS `tokens` (
  `id` int(11) NOT NULL,
  `user_id` int(11) NOT NULL,
  `type` varchar(20) NOT NULL,
  `expiration` datetime NOT NULL,
  `client_id` varchar(80) NOT NULL COMMENT 'Client_ID for the App',
  `value` varchar(80) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
ALTER TABLE `tokens` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=1;

Most OAuth providers allow OAuth access to their backends only to approved applications, so they implement an app registration mechanism, so you need to register your App with them as the first step. This usually results in a pair of keys, the client ID and the client secret, that will uniquely identify your App and serve as an extra security mechanism. The client ID is public, and can be known by the user (and embedded in Apps/websites). The initial interaction, triggered by the user, will require it. Once the 3rd party app and the OAuth provider communicate between them (without the user’s app/browser being involved, steps 4 and 5 in the picture), the client secret, that is kept secure in the custom application backend, is requested by the OAuth provider to validate the identity of the custom App backend.

For simplicity, we will just create a database table for our apps pre-populated with an App, and we will use its client ID and client secret.  In a production environment you would need a web interface to allow 3rd party developers/companies to register their apps.

CREATE TABLE IF NOT EXISTS `apps` (
  `client_id` varchar(80) NOT NULL,
  `client_secret` varchar(80) NOT NULL,
  `name` varchar(255) NOT NULL,
  `user_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `apps` ADD PRIMARY KEY (`client_id`);
INSERT INTO `apps` (`client_id`, `client_secret`, `name`, `user_id`) VALUES
('nkU350F4PfKLf9umf798qX6jlP2ya501', 'v2Q0m12CB5tM36xJ4K4g2Vv06ASD52HD', 'Custom 3rd party App', 1);

My OAuth provider

First we will add an authentication method for our access_token in the REST API of myoauthprovider.com. We will modify api/index.php (the file that contains the main routes for our REST API), adding a middleware “authenticate” method that checks the validity of the api_key header before proceeding to the route. We will use the standard OAuth “Authorization: Bearer xxxxxxxx…x” header.

    } else if (isset($headers["Authorization"])) {
        // are we authenticating through a Bearer access token?
        $authLine = $headers["Authorization"];
        if ($retrievedData = getUserFromAuthorizationHeader($authLine)) {
            global $userData;
            $userData = $retrievedData;
            $response = $next($request, $response);
        } else {
            $invalidCredData = array("error" => true, "msg" => "Authentication error. Access token invalid.");
            echoResponse($response, $invalidCredData, 401);
        }
    } else { // unauthorized.

The method getUserFromAuthorizationHeader($authLine) will read the “Authorization: Bearer <token>” line to parse the token and will validate it in the “tokens” database table.

/**
 * Checks for a valid acess token in the request's headers. The Access Token will
 * be a bearer token in the format: "Bearer as6sab87dasd76asd87asd87asd6f57". If
 * it’s valid, the data for the user is returned, and authentication succeeds.
 * Otherwise, false is returned.
 */
define('OAUTH_BEARER_TOKEN_REGEXP', '/Bearer\s+(?<token>.+)$/');
function getUserFromAuthorizationHeader($authLine) {
    // perform a match for a Bearer token
    preg_match(OAUTH_BEARER_TOKEN_REGEXP, $authLine, $matches);
    if (isset($matches["token"])) { // we do have a token. Now check in ddbb.
        $token = $matches["token"];
        require_once("include/DbHandler.php");
        $db = new \DbHandler();
        return $db->getUserFromBearerAccessToken($token);
    }
    return false;
}

We will define getUserFromBearerAccessToken in DbHandler.php. It will get the information from the user associated with the access token.

/**
 * Checks if the authorization token provided is valid and has not expired.
 * @returns true if there is a valid unexpired token matching the one
 * provided as a parameter, false otherwise.
 */
function getUserFromBearerAccessToken($token) {
  $this->db->where("value", $token)->where("type", “access")->where("expiration >= now()”);
  if ($tokenData = $this->db->getOne("tokens")) { // The access token is valid. Get user
     $this->db->where("id", $tokenData["user_id"]);
     if ($result = $this->db->getOne("users")) { return $result; } else { return false; }
  } else { return false; }
}

Now we need to take care of the different tokens generation mechanism. One of the main pain points of authentication through OAuth tokens is ensuring their uniqueness. For this simple example we will use a quick trick, appending a “userid_” prefix to every token, so a token “1_1bsa768976fh9c8asvcshwdbsa879rv6b” corresponds to the user with ID 1. We will modify the previously existing method randomToken (that generated a random base64 string) to be able to append such prefix:

/**
 * Generates a random token. If a prefix is specified, the token will 
 * start with the string prefix_.
 */
public function randomToken($prefix = null) {
    $token = isset($prefix) ? $prefix . "_" : "";
    $token .= base64_encode(openssl_random_pseudo_bytes(40));
    if (strlen($token) > OAUTH_MAX_TOKEN_LENGTH) {
        $token = substr($token, 0, OAUTH_MAX_TOKEN_LENGTH);
    }
    return $token;
}

Now we can define a method to create each token type: code, access and refresh, with different expiration times depending on the type of token:

/**
 * Generates an Access Token and stores it in the database.
 * @param String $userId User ID identifying the user this request is made for.
 * @param String $clientId Client ID for the App this code is going to be associated to.
 * @returns the generated auth code if it was properly stored, false if there’s an error.
 */
public function generateAccessToken($userId, $clientId) {
    $code = $this->randomToken($userId);
    $data = array(
        "user_id" => $userId,
        "type" => "access",
        "value" => $code,
        "expiration" => $this->db->now("1h"),
        "client_id" => $clientId);
    if ($this->db->insert("tokens", $data)) { return $code; } else { return false; }
}
/**
 * Generates an Refresh Token code and stores it in the database.
 * @param String $userId User ID identifying the user this request is made for.
 * @param String $clientId Client ID for the App this code is going to be associated to.
 * @returns the generated auth code if it was properly stored, false if there’s an error.
 */
public function generateRefreshToken($userId, $clientId) {
    // generate a new one.
    $code = $this->randomToken($userId);
    $data = array(
        "user_id" => $userId,
        "type" => "refresh",
        "value" => $code,
        "expiration" => $this->db->now("1Y"),
        "client_id" => $clientId);
    if ($this->db->insert("tokens", $data)) { return $code; } else { return false; }
}
/**
 * Generates an Access Token code and stores it in the database.
 * @param String $userId User ID identifying the user this request is made for.
 * @param String $clientId Client ID for the App this code is going to be associated to.
 * @returns the generated auth code if it was properly stored, false if there’s an error.
 */
public function generateAuthCode($userId, $clientId) {
    $code = $this->randomToken($userId);
    $data = array(
        "user_id" => $userId,
        "type" => "code",
        "value" => $code,
        "expiration" => $this->db->now("1h"),
        "client_id" => $clientId);
    if ($this->db->insert("tokens", $data)) { return $code; } else { return false; }
}

Note how we associate every token with a user_id AND a client_id. That’s because different applications can use the OAuth services of our provider for the same user. We will need to identify which application is associated with a token. In this scheme, we would surely need some kind of trigger or cronjob to make sure expired tokens get cleaned from the database (i.e: a cronjob that deletes all entries in the “tokens” table whose expiration time has passed). In order to retrieve the information about an app, we will use the method getAppDataByCliendId, defined also in DbHandler.php:

/**
 * Finds the data of an OAuth-validated App and returns it if found.
 * @param String $clientId Client ID for the App to get data from.
 * @returns Array an assoc array with the data of the App, or false if none found.
 */
public function getAppDataByClientId($clientId) {
   $sanitizedId = $this->db->escape($clientId);
   $this->db->where("client_id", $sanitizedId);
   if ($appData = $this->db->getOne("apps")) {
       return $appData;
   } else { return false; }
}

We will also need a method to check if there’s an App with certain Client id and Client secret. This will be our first step when validating an OAuth token.

/**
 * Checks the app data to verify that client ID and client secret are valid.
 * @param String $clientId Client ID for the App.
 * @param String $clienSecret Client Secret for the App.
 * @returns true if the App is valid, false otherwise.
 */
public function checkApp($clientId, $clientSecret) {
    if ($appData = $this->getAppDataByClientId($clientId)) {
        return ($appData["client_secret"] == $clientSecret);
    } else { return false; }
}

The essential step for the authorization process is exchanging the authorization code received from the OAuth provider (upon user authorization) with a valid access token. In order to do that, we will add the function createAccessTokenFromAuthorizationCode in DbHandler.php:

/**
 * Checks the code for the previous authorization request.
 * @param String $code The authorization code that was sent.
 * @param String $clienSecret Client Secret for the App.
 * @returns true if the App is valid, false otherwise.
 */
public function createAccessTokensFromAuthorizationCode($code, $clientId) {
   // check code first.
   $this->db->where("value", $code)->where("client_id", $clientId)->where("type", "code")->where("expiration>=now()");
   if ($codeData = $this->db->getOne("tokens")) {
      if ($accessToken = $this->generateAccessToken($codeData["user_id"], $clientId)) {
          $refreshToken = $this->generateRefreshToken($codeData["user_id"], $clientId);
          $data = array( "access_token" => $accessToken, "expires" => OAUTH_TOKEN_EXPIRATION, "refresh_token" => $refreshToken, "error" => false);
          return $data;
      }
   }
   return false;
}

Last but not least, we will add a method /access_token (GET) in api/index.php. This is the part of the communication done between the 3rd party app’s backed/application and our OAuth backend, without user interaction.

/** OAuth provider routes: Access token returns a token from a valid code. */
$app->post('/access_token', function ($request, $response, $args) {
   $jsonData = $request->getParsedBody();
   if (isset($jsonData["client_id"]) && isset($jsonData["client_secret"]) 
   && isset($jsonData["redirect_uri"]) && isset($jsonData["code"])) {
        // get parameters
        $clientId = $jsonData["client_id"];
        $clientSecret = $jsonData["client_secret"];
        $redirectURI = $jsonData["redirect_uri"];
        $code = $jsonData["code"];
        // check client id and client secret.
        require_once("include/DbHandler.php");
        $db = new DbHandler(); 
        if (!$db->checkApp($clientId, $clientSecret)) {
            return badRequest($response, "Invalid App credentials.");
        }
        // check code and generate access token for the user.
        if ($data = $db->createAccessTokensFromAuthorizationCode(urldecode($code), $clientId)) {
            return echoResponse($response, $data);
        } else { return badRequest($response); }
    } else { return badRequest($response); }
});

Custom 3rd party App.

First we will create a class RESTHandler.php, that will allow us to perform requests to the myoauthprovider.com REST API. The class has the following methods:

authenticateWithMyOauthProvider($redirect_uri, $code): when the user authorizes the 3rd party application from myoauthprovider.com, it will generate a code and redirect the browser to the redirect_uri that customapp.com specified (in our case http://customapp.com:8080/redirect), including this code as a parameter. Upon reception, customapp.com will perform a REST request to myoauthprovider.com /access_token, sending back the obtained code, along with the the new redirect_uri where myoauthprovider will send the access_token in exchange for the code.

getProfileDataFromMyOAuthProvider($access_token): this method calls the /me endpoint (GET) of myoauthprovider.com to get the user profile information, requiring the access token. This call will be used to the 3rd party custom app to get the user info and show that the whole process worked. It’s not part of OAuth itself.

Now we need to do some changes in customapp.com main screen. Currently, index.html is just a plain dumb html file. In order to control the access and refresh tokens, we will use the PHP session variables, so we will first rename it to index.php, and check for $_SESSION variables access_token and refresh_token. If found, we will use getProfileDataFromMyOAuthProvider to retrieve and show the user’s info.

<?php
session_start();
if (isset($_SESSION["access_token"]) && isset($_SESSION["refresh_token"])) { 
    // we already have a session.
    $access_token = $_SESSION["access_token"];
    $refresh_token = $_SESSION["refresh_token"];
    // request data from the OAuth provider
    require_once('RESTHandler.php');
    $rh = new RESTHandler();
    if ($result = $rh->getProfileDataFromMyOAuthProvider($access_token)) {
        $name = $result["name"];
        $email = $result["email"];
    }
}
?>

We’ll check these session variables, alongside the collected data, in the document body:

<?php if (isset($access_token) && isset($refresh_token)) { ?>
<h1>WELCOME TO CUSTOM 3RD PARTY APP!</h1>
  <div class="contact-form">
     <div class="signin">
     <h2>These are your tokens for My OAuth Provider:</h2>
     <p>access token: <?php print $access_token; ?></p>
     <p>refresh token: <?php print $refresh_token; ?></p>  
     <!-- Show user info if available -->
     <?php if (isset($name) && isset($email)) { ?>
        <h2>Data retrieved from My OAuth Provider using that tokens:</h2>
        <p>name: <?php print $name; ?></p>
        <p>email: <?php print $email; ?></p>
     <?php } else { ?>
        <p style="color: red;">Error: I was unable to use that data to retrieve your profile info.</p>
     <?php } ?>
     <button id="logout">Logout</button>
     </div>
  </div>
</div>
<?php } else { ?>
<h1>CUSTOM 3RD PARTY APP LOGIN</h1>
   <div class="contact-form">
      <div class="signin"> 
      <p>Custom 3rd Party App uses the new "My OAuth Provider" for social login. Please, open an account in Sexy Social Login first and then click the button below!</p>
     <button id="sociallogin">Login using My OAuth Provider</button>
     </div>
   </div>
</div>
<?php } ?>

Last, we’ll add some events for the login and logout buttons. This first one will initiate the OAuth process to get the tokens, whereas the second one will destroy the session and token information, effectively logging us out.

 

<script>
/** Do the social login from CustomApp. Call My OAuth Provider. */
$("#sociallogin").click(function (ev) {
   ev.preventDefault();
   var redirectURI = encodeURIComponent("http://customapp.com:8080/redirect.php");
   var clientID = "nkU350F4PfKLf9umf798qX6jlP2ya501";
   var url = "http://myoauthprovider.com:8080/auth.php?redirect_uri=" + redirectURI + "&client_id=" + clientID;
   window.location = url;
});
/** Logout from Custom App: destroy session and related data. */
$("#logout").click(function (ev) {
   ev.preventDefault();
   window.location = "logout.php";
});
</script>

We’ll also add the logout.php file with the PHP code to destroy the session:

<?php
session_start();
unset($_SESSION);
session_destroy();
header("Location: index.php");
?>

Now we need to create the redirect.php file, containing the majority of the OAuth redirection behavior of our 3rd party custom app. It will serve as the redirect_uri to send to myoauthprovider in our OAuth calls.

The first use case is where we are receiving the first response to our initial redirection to myoauthprovider.com, just after the user has (hopefully) authorized our 3rd party app, so we are receiving the initial authorization code.

<?php
session_start();
/** Case 1: first response of OAuth provider w/ the auth code to exchange for a token */
if (isset($_GET["code"]) && isset($_GET["redirect_uri"])) {
   $code = $_GET["code"];
   $redirect_uri = $_GET["redirect_uri"];
   require('RESTHandler.php');
   $rh = new RESTHandler();
   // exchange code for an access token and refresh token.
   if ($result = $rh->authenticateWithMyOAuthProvider($redirect_uri, $code)) 
      // set session parameters
      $_SESSION["access_token"] = $result["access_token"];
      $_SESSION["refresh_token"] = $result["refresh_token"];
     
      // redirect to index.
      header("Location: index.php");
   } else { // error. Code invalid.
      header("Location: unable_authenticate.php");
   }

Our second use case is when we have already sent the auth code, and we receive the access and refresh tokens:

/** Case 2: We receive the access and refresh tokens directly */
} else if (isset($_GET["access_token"]) && isset($_GET["refresh_token"]) &&
isset($_GET["expires"])) {
   // set session parameters
   $_SESSION["access_token"] = $_GET["access_token"];
   $_SESSION["refresh_token"] = $_GET["refresh_token"];
   // redirect to index.
   header("Location: index.php");

Our third use case is when we have previously authenticated with the OAuth provider, so we already have an access token and refresh token defined in our session:

/** Case 3: we already have a session with access & refresh tokens. Redirect to index */
} else if (isset($_SESSION["access_token"]) && isset($_SESSION["refresh_token"])) {
   header("Location: index.php");

The last use case is when there was an error in the protocol. Either the user didn’t authorize the request, or the client id or secret are wrong:

/** Case 4: Authentication error */
} else if (isset($_GET["error"])) {
   $errorType = $_GET["error"];
   $description = isset($_GET["error_description"]) ? "&error_description=" .
                  urlencode($_GET["error_description"]) : "";
   header("Location: unable_authenticate.php?error=" .$errorType . $description);

The unable_authenticate.php file will contain a simple page informing the user about the error, with a brief description. Last, we will redirect to this same file any other request, to have a secure fallback.

/** Else some kind of error happened. Redirect to "unable to authenticate" message */
} else { ob_clean(); header("Location: unable_authenticate.php"); }

It’s important to notice that in this version, expired tokens will continue to exist in the database. We will need a way of cleaning them from time to time, for example, by using a cronjob as explained before, or maybe with a trigger each time a new token is inserted in the database. As they have expired, the tokens don’t imply a problem from the point of view of security, but it’s better to have our database as clean as possible for performance and maintainability reasons.

Version 3

Currently, we are not dealing with the refresh tokens. When the access token expires, we simply need to authorize again to get a new access token. This is not optimal, and that’s the reason why we need the long-lived refresh tokens exist. In this version we will implement and manage our refresh tokens.

My OAuth Provider

First we will add an endpoint to our REST API called /refresh (GET) that will generate a new access token, given a valid refresh token. It will return both tokens along with the expiration date of the new access token:

/** OAuth provider routes: Refresh the access token with a refresh token. */
$app->post('/refresh', function ($request, $response, $args) {
   $jsonData = $request->getParsedBody();
   if (isset($jsonData["client_id"]) && isset($jsonData["client_secret"]) 
      && isset($jsonData["refresh_token"])) {
      // get parameters
      $clientId = $jsonData["client_id"];
      $clientSecret = $jsonData["client_secret"];
      $refreshToken = $jsonData["refresh_token"];
      
      // check client id and client secret.
      error_log("MyOAuthProvider: Checking App...");
      require_once("include/DbHandler.php");
      $db = new DbHandler();
      if (!$db->checkApp($clientId, $clientSecret)) {
         return badRequest($response, "Invalid App credentials. Please check your App ID and Secret.");
      }
      
      // check code and generate access token for the user.
      error_log("MyOAuthProvider: Refreshing access token with refresh token:  $refreshToken");
      if ($data = $db->refreshAccessTokenWithRefreshToken($refreshToken, $clientId)) { 
         return echoResponse($response, $data);
      } else { return badRequest($response); }
   } else { return badRequest($response); }
});

We will implement refreshAccessTokenWithRefreshToken($refreshToken, $cliendId) in DbHandler.php, to check if the refresh token for the app identified with client ID is valid, and in that case, it will generate a new access token and return all the information.

/**
 * Creates a new access token from a refresh token. First it validates the refresh
 * token for the given client ID, and then generates a new access token, stores
 * it in the database and returns it.
 * @param String $refreshToken The refresh token.
 * @param String $clienId Client ID for the App.
 * @returns the new access token data if the refresh token is valid, false otherwise.
 */
public function refreshAccessTokenWithRefreshToken($refreshToken, $clientId) {
   // check refresh token first.
   $this->db->where("value", $refreshToken)->where("client_id", $clientId)->where("type", "refresh")->where("expiration >= now()");
   if ($tokenData = $this->db->getOne("tokens")) {
      $accessToken = $this->generateAccessToken($tokenData["user_id"],$clientId);
      $data = array(
         "access_token" => $accessToken,
         "expires" => OAUTH_TOKEN_EXPIRATION,
         "refresh_token" => $tokenData["value"],
         "error" => false
      );
      return $data;
   }
   return false;
}

As we only want to have one refresh token per user and app, in the method that generates our refresh tokens, we will add a check that will return the current refresh token for that user and app in case one actually exists:

❤️ Enjoying this post so far?

If you find this content useful, consider showing your appreciation by buying me a coffee using the button below 👇.

Buy me a coffeeBuy me a coffee
/**
 * Generates an Refresh Token code and stores it in the database.
 * @param String $userId User ID identifying the user this request is made for.
 * @param String $clientId Client ID for the App this code is going to be associated to.
 * @returns the generated auth code if it was properly stored, false if an error occurred. 
*/
public function generateRefreshToken($userId, $clientId) {
   // try to get the current one first.
   if ($currentToken = $this->getCurrentRefreshToken($userId, $clientId)) {
      return $currentToken;
   }
   // generate a new one.
   $code = $this->randomToken($userId);
   $data = array(
      "user_id" => $userId,
      "type" => "refresh",
      "value" => $code,
      "expiration" => $this->db->now("1Y"),
      "client_id" => $clientId);
   if ($this->db->insert("tokens", $data)) { return $code; } 
   else { return false; }
}
/**
 * Checks if a unexpired refresh token for the user and client IDs already exists.
 * If so, returns it.
 * @param String $userId User ID identifying the user this request is made for.
 * @param String $clientId Client ID for the App using this refresh token.
 * @returns the refresh token if it exists, false otherwise.
 */
public function getCurrentRefreshToken($userId, $clientId) {
   $this->db->where("user_id", $userId)->where("client_id", $clientId)->where("type", "refresh")->where("expiration >= now()");
   if ($tokenData = $this->db->getOne("tokens")) {
      return $tokenData["value"];
   }
   return false;
}

Custom 3rd Party App

The changes in customapp.com involve modifying in index.php the call where we get the user profile data from the OAuth provider REST API once we have our access and refresh token. Now, if the request fails, we will check if its because our access token has expired requesting a new access token and trying again:

if ($result = $rh->getProfileDataFromMyOAuthProvider($access_token)) {
   $name = $result["name"];
   $email = $result["email"];
 } else { // let's try to refresh the token.
   if ($newTokenData = $rh->refreshAccessToken($refresh_token)) { // update access and refresh tokens.
      $access_token = $newTokenData["access_token"];
      $refresh_token = $newTokenData["refresh_token"];
      $_SESSION["access_token"] = $access_token;
      $_SESSION["refresh_token"] = $refresh_token;
      // try to get the profile info again.
      if ($result2 = $rh->getProfileDataFromMyOAuthProvider($access_token)) {
         $name = $result2["name"];
         $email = $result2["email"];
      }
   }
}

The new method refreshAccessToken in RESTHandler.php will take care precisely of calling the /refresh endpoint of myoauthprovider.com to request a new access token.

/**
 * Requests a access token refresh using the refresh token.
 * @returns the new access token if successful. False otherwise.
 */
public function refreshAccessToken($refresh_token) { 
   $url = "http://myoauthprovider.com:8080/api/refresh"; 
   $params = array(
      "client_id" => MYOAUTHPROVIDER_CLIENT_ID,
      "client_secret" => MYOAUTHPROVIDER_CLIENT_SECRET,
      "refresh_token" => $refresh_token
   );
   // perform request and analyze results
   if ($result = $this->performRequest("POST", $url, DECODE_FORMAT_JSON, $params)) {
      if (isset($result["error"]) && ($result["error"] == false)) {
         return $result;
      }
   }
   return false;
}

Version 4

The goal for the final version will be making our OAuth provider smart enough to remember when a user has already authorized an app, so it does not ask the user to authorize the application once the user has already done so.

My OAuth Provider

In auth.php, after getting the app data from getAppDataByClientId(…), we will add a check to find out if the user has already authorized the app.

// Check if a previous auth existed.
if ($authData = $db->previousUserAuthorizationData($userId, $clientId)) {
   $alreadyAuthURI = $redirectURI . "?" . http_build_query($authData);
} else {
   // ... generate the code and redirect to redirect_uri as usual.
}

The previousUserAuthorizationData($userId, $clientId) will check if there’s a refresh token associated with the app, because if it exists, this means we have authorized this app and generated a token pair at least one time. In this case, we will return the associated access token, or a new one if it’s expired, and will send them to the redirect_uri specified (in our case customapp.com:8080/redirect.php), including the tokens to be used directly. So instead of being asked “The application … wants to access your personal information…” and a accept/deny pair of buttons, the user will be presented with a message like “You have already authorized … to access your personal information …, click OK to proceed”. This links with the use case 2 we specified when building our redirect.php file.

/**
 * Checks if the user with userId has already authorized the App with the
 * given clientId to access the API. If so, returns the data for the current
 * authentication tokens (access and refresh).
 */
public function previousUserAuthorizationData($userId, $clientId) {
   // get the refresh token, if any, associated to that user and app.
   $this->db->where("user_id", $userId)->where("client_id", $clientId)->where("type", "refresh")->where("expiration >= now()");
   if ($tokenData = $this->db->getOne("tokens")) {
      // do we have also a valid access token? if not, generate a new valid one.
      $accessToken = $this->getCurrentAccessToken($userId, $clientId);
      if ($accessToken === false) { $accessToken = $this->generateAccessToken($tokenData["user_id"], $clientId); }
      else { // if we had a previously obtained access token, update (renew) the expiration time to OAUTH_TOKEN_EXPIRATION (optional).
         $this->db->where("value", $accessToken);
         $updateTokenData = array("expiration" => $this->db->now("1h"));
         $this->db->update("tokens", $updateTokenData);
      }
      $data = array(
         "access_token" => $accessToken,
         "expires" => OAUTH_TOKEN_EXPIRATION,
         "refresh_token" => $tokenData["value"],
         "error" => false
      );
      return $data;
   }
   return false;
}

Notice how if the access token already exists and is still valid, I am updating its expiration time as if I had just generated a new one, as I think it makes sense that the returned token will last for the entire duration of a new access token (10 minutes). This is not really part of the OAuth specification, and left for the developer to decide.

Now, in auth.php, below, we will be able to tell if there exists previous authorization (and we will show a “go ahead” button) or not, in which case we will ask for the user authorization, like we did before.

<?php if (isset($error)) { ?>
   <h1>Error!</h1> 
   <div class="contact-form">
      <div class="signin">
         <p>Error authorizing app: <?php print $error; ?></p>
      </div>
   </div>
</div>
<?php } else if (isset($alreadyAuthURI)) { ?>
<h1>Authorize <?php print $appData["name"]; ?>!</h1>
   <div class="contact-form">
      <div class="signin">
         <p>You have already authorized the App <?php print $appData["name"]; ?> to access your personal information, including your name and email.</p>
         <button id="authorizeButton">Cool! Let's continue then</button>
      </div>
   </div>
</div>
<?php } else { ?>
<h1>Authorize <?php print $appData["name"]; ?>!</h1>
   <div class="contact-form">
      <div class="signin">
         <p>The App <?php print $appData["name"]; ?> is requesting your permission to access your personal information, including your name and email. Do you want to allow it?
         </p>
         <button id="authorizeButton">Sure! Authorize it!</button>
         <br/><br/>
         <button id="cancelButton">Hell no!</button>
      </div>
   </div>
</div>
<?php } ?>
...
$("#authorizeButton").click(function (ev) {
   ev.preventDefault();
   window.location.href = <?php print '"' . (isset($alreadyAuthURI) ? $alreadyAuthURI : $authorizeURI) . '"'; ?>;
});

In a production scenario, instead of showing a “Ok, you already authorized this app”, and a button, you may want to directly call the redirect back to the customapp.com.

Final thoughts

We have gone through the process of turning your REST backend into a full fledged OAuth provider. From here, there is a lot you can polish or improve in the process, but this tutorial gives you the basic understanding of the process to be able to implement and improve your own implementation of the authorization protocol.

As you may know, I firmly believe that we are wrongly using the OAuth protocol for providing authentication (Single sign-on scenarios), and we should stop doing that. I am in the process of developing a password-less, open source authentication system relying on biometric sensors and asymmetric cryptography. It’s currently being developed, but if you want to know more, you can visit its homepage.