Authentication and Authorization

We use The PHP League’s Oauth 2.0 (https://oauth2.thephpleague.com/) server for the authentication. First of all we need to add our own module named oauth under the module directory and install the package via composer

composer require league/oauth2-server

If you look at our router, you will see that an authentication request is separately considered, unlike the routing.json file scan for each module to find the acting module. Therefore the endpoints that we will create in oauth module’s routing.json file will pass to the oauth module even though the user is not authenticated.

The module structure is as follows:

- modules
    - oauth
        - src
            - Entities
                - AccessTokenEntity.php
                - AuthCodeEntity.php
                - ClientEntity.php
                - RefreshTokenEntity.php
                - ScopeEntity.php
                - UserEntity.php
            - Repositories
                - ​​AccessTokenRepository.php
                - AuthCodeRepository.php
                - ClientRepository.php
                - RefreshTokenRepository.php
                - ScopeRepository.php
                - UserRepository.php
            - oauth.php
            - pre_schema_data.json
            - routing.json
            - Schemas.json

As you can guess, the code under the src folder is borrowed from the php league and pretty standard. We just needed to modify the code for our own needs. What is important here is to take advantage of the oauth server and store the tokens in our database, flush them when they expire, and integrate our own login mechanism to check credentials. Entities are pretty standard, but the repositories need to be modified.

AccessTokenRepository.php

​​<?php
namespace modules\oauth\Repositories;
 
use \League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use \League\OAuth2\Server\Entities\ClientEntityInterface;
use \League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface;
use \modules\oauth\Entities\AccessTokenEntity;
use \modules\database\database;
 
class AccessTokenRepository implements AccessTokenRepositoryInterface
{
   public $db;
   public function __construct(){
       $this->db = new database;
   }
 
   /**
    * {@inheritdoc}
    */
   public function getNewToken(ClientEntityInterface $clientEntity, array $scopes, $userIdentifier = null)
   {
       $accessToken = new AccessTokenEntity();
       $accessToken->setClient($clientEntity);
       foreach ($scopes as $scope) {
           $accessToken->addScope($scope);
       }
       $accessToken->setUserIdentifier($userIdentifier);
 
       return $accessToken;
   }
 
   /**
    * {@inheritdoc}
    */
   public function persistNewAccessToken(AccessTokenEntityInterface $accessTokenEntity)
   {
       // Some logic here to save the access token to a database
       $timestamp = $accessTokenEntity->getExpiryDateTime()->getTimestamp();
       $newAccessToken = [
           'token'       => $accessTokenEntity->getIdentifier(),
           'client_id'   => $accessTokenEntity->getClient()->getIdentifier(),
           'expire_time' => $timestamp,
       ];
      
       if (!is_null($accessTokenEntity->getUserIdentifier())) {
           $newAccessToken['user_id'] = $accessTokenEntity->getUserIdentifier();
       }
 
       if ($accessTokenEntity->getScopes() !== []) {
           $scopes = array_map(function ($Scope) {
               /* @var ScopeEntity $Scope */
               return $Scope->getIdentifier();
           }, $accessTokenEntity->getScopes());
       }
       $newAccessToken['scopes'] = json_encode($scopes);
 
       $this->db->insert($newAccessToken,"oauth_access_token");
 
   }
 
   /**
    * {@inheritdoc}
    */
   public function revokeAccessToken($token_id)
   {
       $identifier = array(
           "token" => $token_id
       );
       $this->db->delete($identifier,"oauth_access_token");
   }
 
   public function find_access_token($token_id)
   {
       $data = $this->db->select(
           "oauth_access_token",
           array("token"=>$token_id),
       );
       return $data;
   }
 
   /**
    * {@inheritdoc}
    */
   public function isAccessTokenRevoked($token_id)
   {
       $accessToken = $this->find_access_token($token_id);
       if (!$accessToken) {
           return true;
       }
       return false; // Access token hasn't been revoked
   }
 
   /**
    * Removes expired access tokens.
    */
   public function removeExpired()
   {
       $identifier = array(
           "expires_at" => array(
               "operator" => "<",
               "value" => time()
           )
       );
       $this->db->delete($identifier,"oauth_access_token");
   }
}
 

Obviously, every repository class is an implementation of interfaces defined on the package. We start with defining the database object for simply database functionality.

Lets see the function of each method:

getNewToken

When a user is logged in, this method defines the user and adds the scopes to the object. A scope is basically a permission. This way, after the user is authenticated, we will have access to the scopes easily.

persistNewAccessToken

After we authenticate the user, the system generates an access token. Oauth package by itself can not handle the token storage, this method helps us to do that.

revokeAccessToken

When an access token is expired, we should delete that from the database.

find_access_token

After the authentication, every request contains Bearer access token information. To make sure that the token is valid, we search for the access token in our own database.

isAccessTokenRevoked

The module checks if an access token is revoked or not with this method.

removedExpired

The module removes all the expired tokens at once.

AuthCodeRepository.php

<?php
namespace modules\oauth\Repositories;

use \League\OAuth2\Server\Entities\AuthCodeEntityInterface;
use \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface;
use \modules\oauth\Entities\AuthCodeEntity;
use \modules\database\database;

class AuthCodeRepository implements AuthCodeRepositoryInterface
{
    public $db;
    public function __construct(){
        $this->db = new database;
    }
    /**
     * {@inheritdoc}
     */
    public function persistNewAuthCode(AuthCodeEntityInterface $authCodeEntity)
    {
        $newAuthCode = [
            'token'       => $authCodeEntity->getIdentifier(),
            'expire_time' => $authCodeEntity->getExpiryDateTime(),
            'client_id'   => $authCodeEntity->getClient()->getIdentifier(),
        ];

        if (!is_null($authCodeEntity->getUserIdentifier())) {
            $newAuthCode['user_id'] = $authCodeEntity->getUserIdentifier();
        }
        if ($authCodeEntity->getScopes() !== []) {
            $scopes = array_map(function ($Scope) {
                /* @var ScopeEntity $Scope */
                return $Scope->getIdentifier();
            }, $authCodeEntity->getScopes());
        }
        
        $this->db->insert($newAuthCode,"oauth_auth_code");
    }

    public function find_auth_code($code_id)
    {
        $data = $this->db->select(
            "oauth_auth_code",
            array("token"=>$code_id),
        );
        return $data;
    }

    /**
     * {@inheritdoc}
     */
    public function revokeAuthCode($code_id)
    {
        $identifier = array(
            "token" => $code_id
        );
        $this->db->delete($identifier,"oauth_auth_code");
    }

    /**
     * {@inheritdoc}
     */
    public function isAuthCodeRevoked($code_id)
    {
        $auth_code = $this->find_auth_code($code_id);
        if (!$auth_code) {
            return true;
        }
        return false; // Auth code hasn't been revoked
    }

    /**
     * {@inheritdoc}
     */
    public function getNewAuthCode()
    {
        return new AuthCodeEntity();
    }
}

We start by defining our database object again since we will need manipulation over the database.

Authorization code: An intermediary token generated when a user authorizes a client to access protected resources on their behalf. The client receives this token and exchanges it for an access token.

We store the authorization tokens just like the access tokens in the database.

persistNewAuthCode

Stores the generated auth code in the database with an expiration date and scopes.

find_auth_code

Search for an auth code.

revokeAuthCode

Remove the auth code from the database/

isAuthCodeRevoked

Check if the auth code is deleted or not.

getNewAuthCode

Start a new process to create a new auth code

ClientRepository.php

<?php

namespace modules\oauth\Repositories;

use \League\OAuth2\Server\Repositories\ClientRepositoryInterface;
use \modules\oauth\Entities\ClientEntity;
use \modules\database\database;

class ClientRepository implements ClientRepositoryInterface
{
    const CLIENT_NAME = $_ENV['AUTH_CLIENT_NAME'];
    const REDIRECT_URI = $_ENV['AUTH_REDIRECT_URI'];

    public $db;
    public function __construct(){
        $this->db = new database;
    }

    /**
     * {@inheritdoc}
     */
    public function getClientEntity($clientIdentifier)
    {
        $client = new ClientEntity();

        $client_control = $this->db->select("oauth_clients",array("name"=>$clientIdentifier),$total=1);

        $client->setIdentifier($clientIdentifier);
        $client->setName($client_control[0]->name);
        $client->setRedirectUri($client_control[0]->redirect_uri);
        $client->setConfidential($client_control[0]->is_confidential);

        return $client;
    }

    /**
     * {@inheritdoc}
     */
    public function validateClient($clientIdentifier, $clientSecret, $grantType)
    {
        $client_control = $this->db->select("oauth_clients",array("name"=>$clientIdentifier),$total=1);

        if($client_control){
            if($client_control[0]->is_confidential){
                return password_verify($clientSecret, $client_control[0]->secret);
            }
            
            return true;
        }

        return false;
    }
}

We start with the database object as usual.

getClientEntity

We have a table to store the client credentials in our database. A client is a medium talking with the server on behalf of the client. It can be a mobile app, a web app or a desktop app. Whatever it is, to be able to communicate with the server, this medium requires a sort of identification. If you have used any sort of API before, you would know that the service generates an API key and a Secret before anything. This client information would be that in this example.

The module asks for this information at the beginning of the whole process, checks if such a client exists in our database and returns that data.

validateClient

Client validation is processed in this method.

RefreshTokenRepository.php

<?php
namespace modules\oauth\Repositories;

use \League\OAuth2\Server\Entities\RefreshTokenEntityInterface;
use \League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
use \modules\oauth\Entities\RefreshTokenEntity;
use \modules\database\database;

class RefreshTokenRepository implements RefreshTokenRepositoryInterface
{
    public $db;
    public function __construct(){
        $this->db = new database;
    }
    /**
     * {@inheritdoc}
     */
    public function persistNewRefreshToken(RefreshTokenEntityInterface $refreshTokenEntity)
    {
        // Some logic to persist the refresh token in a database
        $timestamp = $refreshTokenEntity->getExpiryDateTime()->getTimestamp();
        $refreshToken = [
            'token'           => $refreshTokenEntity->getIdentifier(),
            'access_token_id' => $refreshTokenEntity->getAccessToken()->getIdentifier(),
            'expire_time'     => $timestamp,
        ];

        $this->db->insert($refreshToken,"oauth_refresh_token");
    }

    /**
     * {@inheritdoc}
     */
    public function revokeRefreshToken($token_id)
    {
        // Some logic to revoke the refresh token in a database
        $identifier = array(
            "token" => $token_id
        );
        $this->db->delete($identifier,"oauth_refresh_token");
    }

    public function find_refresh_token($token_id)
    {
        $data = $this->db->select(
            "oauth_refresh_token",
            array("token"=>$token_id),
        );
        return $data;
    }

    /**
     * {@inheritdoc}
     */
    public function isRefreshTokenRevoked($token_id)
    {
        $refreshToken = $this->find_refresh_token($token_id);
        if (!$refreshToken) {
            return true;
        }
        return false; // Access token hasn't been revoked
    }

    /**
     * {@inheritdoc}
     */
    public function getNewRefreshToken()
    {
        return new RefreshTokenEntity();
    }
}

We start with the database connection first.

persistNewRefreshToken

The lifetime of an access token is 1 hour. The system generates a refresh token with every access token generation. After the expiration of the access token, the client needs to generate a new one without prompting the password of the end user. The refresh token is used to generate an access token. That’s why we have them. When we re-generate an access token, the refresh token is generated as well and the old one expires.

This method stores the refresh token data in our database With an expiration date of 30 days.

revokeRefreshToken

Removes the access token from the database.

find_refresh_token

Search for a refresh token in the database.

isRefreshTokenRevoked

Check if the refresh token is removed.

getNewRefreshToken

Generates a new refresh token object from scratch.

ScopeRepository.php

<?php
namespace modules\oauth\Repositories;

use \League\OAuth2\Server\Entities\ClientEntityInterface;
use \League\OAuth2\Server\Repositories\ScopeRepositoryInterface;
use \modules\oauth\Entities\ScopeEntity;
use \modules\user\user;
use \modules\database\database;

class ScopeRepository implements ScopeRepositoryInterface
{
    public $db;
    public function __construct(){
        $this->db = new database;
    }
    /**
     * {@inheritdoc}
     */
    public function getScopeEntityByIdentifier($scopeIdentifier)
    {
        $scopeIdentifier = str_replace("+"," ",$scopeIdentifier);
        $permissions = $this->db->select(
            "permissions","",-1
        );

        $scopes = array();
        
        foreach ($permissions as $permission) {
            $scopes[] = $permission->name;
        }

        if (!in_array($scopeIdentifier, $scopes)) {
            return;
        }

        $scope = new ScopeEntity();
        $scope->setIdentifier($scopeIdentifier);

        return $scope;
    }

    /**
     * {@inheritdoc}
     */
    public function finalizeScopes(
        array $scopes,
        $grantType,
        ClientEntityInterface $clientEntity,
        $userIdentifier = null
    ) {
        $user = new user($userIdentifier);
        $permissions = $user->all_user_permissions();

        foreach ($permissions as $permission) {
            $scope = new ScopeEntity();
            $scope->setIdentifier($permission["name"]);
            $scopes[] = $scope;
        }

        return $scopes;
    }
}

We start by defining the database connection.

getScopeEntityByIdentifier

A scope is a permission. When we use scope, we mean a permission used by the oauth server, on the other hand, we use the term permission for a framework permission.

We have a permissions table in our database. When we receive a scope from a request, we check if that scope does exist in our database by searching for it. This method returns the scope if found, returns empty if nothing is found.

finalizeScopes

A user has certain permissions. We load the user in this method and assign all their permissions so that we can check if an authenticated user has access to a certain call.

UserRepository.php

<?php
namespace modules\oauth\Repositories;

use \League\OAuth2\Server\Entities\ClientEntityInterface;
use \League\OAuth2\Server\Repositories\UserRepositoryInterface;
use \modules\oauth\Entities\UserEntity;
use \modules\database\database;
use \modules\user\user;

class UserRepository implements UserRepositoryInterface
{
    public $db;
    public function __construct(){
        $this->db = new database;
    }
    /**
     * {@inheritdoc}
     */
    public function getUserEntityByUserCredentials(
        $username,
        $password,
        $grantType,
        ClientEntityInterface $clientEntity
    ) {
        $user = new user;
        $login = $user->login_with_credentials($username,$password);
        
        if ($login) {
            $user_entity = new UserEntity();
            $user_entity->setIdentifier($login);
            return $user_entity;
        }

        return false;
    }
}

We load the database connection first.

getUserEntityByUserCredentials

This is where we check the user credentials for authentication. Returns the user entity on success.

routing.json

{
    "routes": [
        {
            "name": "Access token authorization",
            "path": "/access_token",
            "method": "POST",
            "actor": "modules\\oauth\\oauth",
            "handler": "access_token",
            "permissions": [
                {
                    "type": "standart",
                    "name": "access site"
                }
            ]
        },
        {
            "name": "Refresh token",
            "path": "/refresh_token",
            "method": "POST",
            "actor": "modules\\oauth\\oauth",
            "handler": "refresh_token",
            "permissions": [
                {
                    "type": "standart",
                    "name": "access site"
                }
            ]
        },
        {
            "name": "Create new client",
            "path": "/create_client",
            "method": "POST",
            "actor": "modules\\oauth\\oauth",
            "handler": "create_client",
            "permissions": [
                {
                    "type": "standart",
                    "name": "create client"
                }
            ]
        }
    ]
}

We define the endpoints in the routing.json file.

/access_token: This is where we send the user credentials and generate the access token. This token will be used on every other request made to the server to make sure the user is authorized to access those endpoints.

/refresh_token: When the access token expires, the client needs to regenerate one by using this token.

/create_client: This is for admins to create a new client.

schemas.json

{
    "tables": [
        {
            "name" : "oauth_access_token",
            "fields": [
                {
                    "name": "token",
                    "type": "string",
                    "options" : {
                        "length": 255,
                        "notNull": false
                    }
                },
                {
                    "name": "client_id",
                    "type": "string"
                },
                {
                    "name": "expire_time",
                    "type": "integer"
                },
                {
                    "name": "user_id",
                    "type": "integer"
                },
                {
                    "name": "scopes",
                    "type": "text"
                }
            ]
        },
        {
            "name" : "oauth_auth_code",
            "fields": [
                {
                    "name": "token",
                    "type": "string",
                    "options" : {
                        "length": 255,
                        "notNull": false
                    }
                },
                {
                    "name": "client_id",
                    "type": "string"
                },
                {
                    "name": "expire_time",
                    "type": "integer"
                },
                {
                    "name": "user_id",
                    "type": "integer"
                },
                {
                    "name": "scopes",
                    "type": "text"
                }
            ]
        },
        {
            "name" : "oauth_refresh_token",
            "fields": [
                {
                    "name": "token",
                    "type": "string",
                    "options" : {
                        "length": 255,
                        "notNull": false
                    }
                },
                {
                    "name": "access_token_id",
                    "type": "string"
                },
                {
                    "name": "expire_time",
                    "type": "integer"
                }
            ]
        },
        {
            "name" : "oauth_clients",
            "fields": [
                {
                    "name": "name",
                    "type": "string",
                    "options" : {
                        "length": 255,
                        "notNull": false
                    }
                },
                {
                    "name": "secret",
                    "type": "string"
                },
                {
                    "name": "is_confidential",
                    "type": "boolean"
                },
                {
                    "name": "redirect_uri",
                    "type": "text"
                },
                {
                    "name": "created_at",
                    "type": "integer"
                },
                {
                    "name": "active",
                    "type": "boolean"
                }
            ]
        }
    ]
}

We have for tables

oauth_access_token: Stores the access tokens with scopes and expiration dates.

oauth_auth_code: Stores the authorization codes with expiration dates and scopes.

oauth_refresh_token: Stores the refresh tokens.

oauth_clients: Stores the client information.

pre_schema_data.json

Sets permissions on installation.

oauth.php

<?php

namespace modules\oauth;

use League\OAuth2\Server\Grant;
use League\OAuth2\Server\AuthorizationServer;
// use Psr\Http\Message\{ServerRequestInterface,ResponseInterface};
use GuzzleHttp\Psr7\ServerRequest as Request;
use GuzzleHttp\Psr7\Response;

use \modules\oauth\Entities;
use \modules\oauth\Repositories;
use \modules\oauth\Entities\ClientEntity;

class oauth extends \modules\system\system{

    public $oauth_server;
    public $request;
    public $response;
    
    public function access_token($data){
        // Init our repositories
        $clientRepository = new Repositories\ClientRepository(); // instance of ClientRepositoryInterface
        $scopeRepository = new Repositories\ScopeRepository(); // instance of ScopeRepositoryInterface
        $accessTokenRepository = new Repositories\AccessTokenRepository(); // instance of AccessTokenRepositoryInterface
        $userRepository = new Repositories\UserRepository(); // instance of UserRepositoryInterface
        $refreshTokenRepository = new Repositories\RefreshTokenRepository(); // instance of RefreshTokenRepositoryInterface

        // Path to public and private keys
        $privateKey = 'file://'.$_ENV['OAUTH_PRIVATE_KEY'];
        $encryptionKey = $_ENV['OAUTH_ENCRYPTION_KEY'];

        // Setup the authorization server
        $this->oauth_server = new AuthorizationServer(
            $clientRepository,
            $accessTokenRepository,
            $scopeRepository,
            $privateKey,
            $encryptionKey
        );

        $client = new ClientEntity;
        $userRepository->getUserEntityByUserCredentials($data["username"],$data["password"],"password",$client);

        $grant = new \League\OAuth2\Server\Grant\PasswordGrant(
            $userRepository,
            $refreshTokenRepository
        );

        $grant->setRefreshTokenTTL(new \DateInterval('P1M')); // refresh tokens will expire after 1 month

        // Enable the password grant on the server
        $this->oauth_server->enableGrantType(
            $grant,
            new \DateInterval('PT1H') // access tokens will expire after 1 hour
        );

        $request = new Request(
            "POST",
            $_ENV[‘AUTH_REDIRECT_URI'],
            // ["Content-Type"=>"application/json/x-www-form-urlencoded"],
            // http_build_query($data)
        );
        $new_request = $request->withParsedBody($data);

        try {
            $response = new Response;
            $access_token_response = $this->oauth_server
                ->respondToAccessTokenRequest($new_request,$response);
    
            $accessTokenData = json_decode((string) $access_token_response->getBody(), true);
    
            return array("output"=>$accessTokenData);

        } catch (\Throwable $th) {
            header('HTTP/1.1 403 Forbidden');
            print json_encode(array("message"=>"Access token can not be created, either the user credentials are wrong or the request is malformed/missing required elements"));
            exit();
        }
    }

    public function refresh_token($data){
        // Init our repositories
        $clientRepository = new Repositories\ClientRepository(); // instance of ClientRepositoryInterface
        $accessTokenRepository = new Repositories\AccessTokenRepository(); // instance of AccessTokenRepositoryInterface
        $scopeRepository = new Repositories\ScopeRepository(); // instance of ScopeRepositoryInterface
        $refreshTokenRepository = new Repositories\RefreshTokenRepository(); // instance of RefreshTokenRepositoryInterface

        $privateKey = 'file://'.$_ENV['OAUTH_PRIVATE_KEY'];
        $encryptionKey = $_ENV['OAUTH_ENCRYPTION_KEY'];

        // Setup the authorization server
        $this->oauth_server = new AuthorizationServer(
            $clientRepository,
            $accessTokenRepository,
            $scopeRepository,
            $privateKey,
            $encryptionKey
        );

        $grant = new \League\OAuth2\Server\Grant\RefreshTokenGrant($refreshTokenRepository);
        $grant->setRefreshTokenTTL(new \DateInterval('P1M')); // new refresh tokens will expire after 1 month

        // Enable the refresh token grant on the server
        $this->oauth_server->enableGrantType(
            $grant,
            new \DateInterval('PT1H') // new access tokens will expire after an hour
        );

        $all_headers = getallheaders();
        // var_dump($all_headers);
        $all_headers["Content-Type"] = "application/x-www-form-urlencoded";
        $request = new Request(
            "POST",
            $_ENV[‘AUTH_REDIRECT_URI'],
            $all_headers,
            // ["Content-Type"=>"application/json/x-www-form-urlencoded"],
            http_build_query($data)
        );
        $new_request = $request->withParsedBody($data);

        try {
            $response = new Response;
            $access_token_response = $this->oauth_server
                ->respondToAccessTokenRequest($new_request,$response);

            $accessTokenData = json_decode((string) $access_token_response->getBody(), true);

            return array("output"=>$accessTokenData);

        } catch (\Throwable $th) {
            header('HTTP/1.1 403 Forbidden');
            print json_encode(array("message"=>"Refresh token not found"));
            exit();
        }

    }

    public function create_client($client_data){
        $client_data["secret"] = password_hash($client_data["secret"],PASSWORD_DEFAULT);
        $client_data["created_at"] = time();
        $result = $this->db->insert($client_data,"oauth_clients");
        if($result){
            return array("output"=>array("id"=>$result));
        }
        else{
            return array("output"=>array("message"=>"Failed"));
        }
        
    }

    public function bearer_validator($token){
        // Init our repositories
        $accessTokenRepository = new Repositories\AccessTokenRepository(); // instance of AccessTokenRepositoryInterface

        // Path to authorization server's public key
        $publicKeyPath = $_ENV['OAUTH_PUBLIC_KEY'];
                
        // Setup the authorization server
        $server = new \League\OAuth2\Server\ResourceServer(
            $accessTokenRepository,
            $publicKeyPath
        );

        $all_headers = getallheaders();
        $all_headers["Content-Type"] = "application/x-www-form-urlencoded";

        $request = new Request(
            "POST",
            $_ENV[‘AUTH_REDIRECT_URI'],
            $all_headers
            // http_build_query($data)
        );

        $middleware = new \League\OAuth2\Server\Middleware\ResourceServerMiddleware($server);
        $mid_response = $middleware->__invoke(
            $request,
            new Response(),
            function () {
                return \func_get_args();
            }
        );
        return $mid_response;
    }
}

This file is where the actual request is handled. If you check the routing.json file, you will see the requests are first directed to methods in this module and then we ask the oauth server to do it’s magic. During that process, we manipulated the data stored in the database using the repository methods.

access_token

The /access_token request is directed here with the header information.

We initialize the request, invoke the repositories, set up the authorization server, check the user credentials, create a refresh token and try to return a response.

The request should be as follows:

POST to /access_token

Content-Type: text/plain
{
  "grant_type": "password",
  "client_id": "my_web_client",
  "client_secret": "nghtyH64GBNh.GOSd!",
  "scope": "access+site",
  "username": "[email protected]",
  "password": "1234"
}

On success, it returns:

HTTP 200 OK

{
    "token_type": "Bearer",
    "expires_in": 3600,
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJ3b2RvX3dlYl9jbGllbnQiLCJqdGkiOiJlZjdjNGY5NGUzOWViNTg2NDc4ZWZjNjhjNmVhZjIwNGM1MDcyODI5NjQxNWFiOGVlOWUyZDAxZGZlNjM1NzJlMjc3ZjYzMzlhNjg0MDc2YyIsImlhdCI6MTYyMTUwNzU0MS43NzAxMDUsIm5iZiI6MTYyMTUwNzU0MS43NzAxMTYsImV4cCI6MTYyMTUxMTE0MS43NTU5ODMsInN1YiI6IjIiLCJzY29wZXMiOlsiYWNjZXNzIHNpdGUiLCJnZXQgb3duIGVudGl0eSIsImNyZWF0ZSBlbnRpdHkiLCJ1cGRhdGUgb3duIGVudGl0eSIsImRlbGV0ZSBvd24gZW50aXR5IiwiZ2V0IG93biBnYW1lIiwiY3JlYXRlIGdhbWUiLCJ1cGRhdGUgb3duIGdhbWUiLCJkZWxldGUgb3duIGdhbWUiLCJnZXQgb3duIHVzZXIiLCJ1cGRhdGUgb3duIHVzZXIiLCJkZWxldGUgb3duIHVzZXIiLCJjcmVhdGUgY2xpZW50Il19.P7FQ0DB9jODsSD4j9KZ4Qs8FRWyykHCulez1ajODpSQyEHvPnU8kTqQ_dsL9WdYErd7VzGlPXiOCYj9-5dqX-qIn6SWyMGcZCUE-LfdxbAPAjvqlvGa2RSZtBtiSGG0pW80x7y640a7DxMlR6b6sdJE5_yJArz4pV5l83mcf1pF6CVBuQh6bAS1wIbUtXAltTBeNfC5D4pN8aq2fGUpAoNSJEPyJAQ6JCnBAwIPpjgMLsm7yAs4z1uNDDl9W3VFZ0yUBW3F2LSzDxiELNinCjG7njZ8m5jhsBgo0hrk-A6Xf9sESTND-ymQy0wCuaTkzotU53Hyh5JbrWITUa-dqVw",
    "refresh_token": "def50200334206ad1405750f53e806759ebf9fef1aec53ee996cfe927e80d2111abd2ecb61aae7a3e476cd81eced8f52d78742f723e6157035e87bf45cfe0b6343019ed85ec85907133d158bb04ea53ae72cd62504ac2f8869251fa8b0cb57da0340626010f477ba180522df7a2bac85e12f37ab56742ada7ca2ff83b79bd4ed99c897cc680e7c27532e979d2bf417c7f1ffc2e0e5be6f11d6700f019f4a08320ee13e37149add810347cfd7efec033976748cc0c3a5e7d200fa6c2a299ea71aa17b086b91d1d11eef052230d239734fcd44e4acaafefa7dcbedbec35005f150d3aac003af876578725d86fb558191be4633884309ff8a5a8381a3c18719241d318d1a56d08e5ac29154764e678a4a90352c5f72cf585867ebc6685446c09926b6839e9c5f2ddf5b62a92e59e7aac249aea62134ca0e28810614b339dfa3225a1bcfc20aeddf7ff6ac5db5de47eef1c057d48339083e4b395b2c28cbfa77d171bcddf1433ca3aeb1b0193a355ccef676ed819cb3911d1904683932bd836b54ff4ae8a92089a9c544c755065621cbd5593a3aeffd7c3d6827b1f27efa18d05188cfe103b8f07c1e863a6132ddd0a861ff8a0def0feb07b0fb3ddad1b3a17e50f9319ae4fadde64e665c792697be60df05f0a6f24b07707e65bcb46683223c571e0e842dd020654898f2afe127b4334174f559f3d1c36982ca306e6acd71f85f29ff7b1c45dd5ba87827821c95d5a83a65cbf3c876aef580682f3e7e02e8a47cf3daca042af0ae9393fe4f695956f708deda27ca911a53f77072506631b33ed63bee6a0697290f50ba172d5f"
}

On failure it returns:

HTTP 403 Forbidden

{
    "message": "Access token can not be created, either the user credentials are wrong or the request is malformed/missing required elements"
}

refresh_token

The /refresh_token endpoint is directed to this method. When the client needs to refresh the token, this is where they end up.

The request should be as follows:

POST /refresh_token with the header information:

Content-Type: text/plain
{
  "grant_type": "refresh_token",
  "client_id": "my_web_client",
  "client_secret": "nghtyH64GBNh.GOSd!",
  "scope": "access+site",
  "refresh_token": "def50200804a43535828618ed66d2ee..." // long refresh token generated during the access token request
}

On success

HTTP 200 OK

{
    "token_type": "Bearer",
    "expires_in": 3600,
    "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJ3b2RvX3dlYl9jbGllbnQiLCJqdGkiOiJhMDUyZmUwZjUxZWViOTVkZDRjMDVhYjc5ZDA1YTczZmExMGRjNDRjNzA0ZTFkZTI3MWZmZWMzNTA1NTk5NjE5MzI0OTQ0YWUyODAwN2ZlMyIsImlhdCI6MTYyMTcxNTY5Ny4wNTE4OCwibmJmIjoxNjIxNzE1Njk3LjA1MTg5OCwiZXhwIjoxNjIxNzE5Mjk3LjAzNzkxNCwic3ViIjoiNSIsInNjb3BlcyI6WyJhY2Nlc3Mgc2l0ZSJdfQ.bzltGUSsGiNl7lMZFf6qTdmUoYGM_wk7-xWrqHyuq6LcTH8nKkm-TqCi-Tm39VSa8Q8CgmW3T9QWxhTDhf9SMXuTlLZbDFEq24PB6ga78RPxeg_Cf3ufGyqUd_dBObfq7mCAqBh7UsIapJrUvDF9TDrGFce0mqGYmJyl4ZLK2_3KLY1fH1NpNTJU2SbPnY6l4pjNx-CVt7KtUxTK_W673IRy0YFqfE54qguagqrQVMAcBPI4uVxKFZUJJ6i3PH5lipd3i_ku9rgBLOHp4ril4ErhYw4ESTkZtEFdRL_W-UC3S31ThO726ACQvv8e2RkxkN0pri1P8bZvzLql7e8ICQ",
    "refresh_token": "def50200f2ca9d1165ae78d4c0fdd89e370dc12fd7ff2262537a20ed3367190b09ab7a79636c66313e3a94dc6c243df8f454973de8ae05250ffc3b40e162c35064558f00b529868c295f1646f743c0a2c2596d75a9e4cb8e84365b8f3c019ae13f9298ba5288bd266f4638b391af49c3907e6b13c058e19a43da4ec90806d9e418e1463de7d5aafb8f1bbf75404a6164e4070e4f3523ea1a325176bf82e911a578800292d05790c2df679c0e9d9d3537ff7fcd34b822b3a06cb53b0acce14a26f04fb69ac708064e63e7501b96abe7482095ce944f5131627c81752d016f61cf5884c155dc35a2bba7bc615a0efd9136c8a974cdf0572589870f3bbea08707223a5df08e037d724eb820e57c4349a39989e92542aca0222ffa0f0984fc1cde5da435ff1e95d372a9e678d3d7ca75ad4c7dbc148cd059403ca4a4c67e7fc584bbc0ad1b53ba68499619345dba7b4b6acccb4f17eb866dee15b13cbae8c06401b38e0fa48f9687be008b375be90949ee8e19c254d9464993b8d76856837ce6"
}

On failure

HTTP 403 Forbidden

{
    "message": "Refresh token not found"
}

Note that the old refresh token will be revoked once it’s used.

create_client

This method is used to serve the /create_client endpoint. You need to set proper permissions before using this method, by default, only the admin user role has access to this.

A request should be as follows:

POST /create_client with the header information

Content-Type: text/plain
{
    "name": "Your client name",
    "secret": "Your client password",
    "is_confidential": true, // this is usually true
    "redirect_uri": "http://wodo.io",
    "active": 1
}

Note that any http request other than access token and the refresh token requests must contain the Authorization header with the Bearer information. This request is no exception/

bearer_validator

When there is a request with the Authorization header and the Bearer information, we need to validate that token, this method handles that.

Conclusion

Looks like we have covered every class and method in the oauth module. To summarize the process, let’s look at the key points on the lifetime of a request.

Understanding Oauth is not an easy task, I’d suggest you to play with Postman, create a few users, login with credentials. I have provided examples above, all you need is to experiment a bit until you get comfortable with the mechanism.