API development

On this side of the project, we need to integrate the OAuth 2.0 component in order to generate access tokens. OAuth 2.0 gives the possibility of using different grant types. The simplest one is the Client Credentials Grant. In this scenario, the client can request an access token by using only its client credentials. Using this type of grant, we can skip the authorization step and after verifying the credentials of the user we can generate an access token.

The module structure

For the functionality we are implementing, we will add a new module named OAuth, and we will also create a new listener on the Common module. The folder structure will look similar to the following screenshot:

The module structure

Installing the OAuth 2.0 component

The first thing we need to do is install the OAuth2.0 component we mentioned before on our project. The task is very easy and will be handled by a composer. Open the composer.json file and place the following code in the require section:

"bshaffer/oauth2-server-php": "v1.0"

After that, you need to inform the composer to update everything. This can be accomplished by running the following command on a terminal window inside the root folder of the project:

phpcomposer.phar update

This will update all the dependencies and libraries to the previous version and will also install the new component under the vendor folder.

This component will be using the database, so we need to create a few tables. The following are the queries, which we need to run in order to create new tables. These queries are extracted directly from the documentation of the component we are using.

CREATE TABLE oauth_clients (client_id TEXT, client_secret TEXT, redirect_uri TEXT);

CREATE TABLE oauth_access_tokens (access_token TEXT, client_id TEXT, user_id TEXT, expires TIMESTAMP, scope TEXT);

CREATE TABLE oauth_authorization_codes (authorization_code TEXT, client_id TEXT, user_id TEXT, redirect_uri TEXT, expires TIMESTAMP, scope TEXT);

CREATE TABLE oauth_refresh_tokens (refresh_token TEXT, client_id TEXT, user_id TEXT, expires TIMESTAMP, scope TEXT);

Modifying LoginController.php

In this controller, we need to check if the user has sent the correct username and password in order to create an access token. By using the access token, the user will be able to issue calls to the API. Let's review the changes we have to do in the create() method.

use OAuth2StoragePdo;
use OAuth2Server;
use OAuth2GrantTypeClientCredentials;
use OAuth2Request;
use OAuth2Response;

These are the lines of code we need to add at the beginning. As we are using an external library to handle all the OAuth 2.0 operations, we need to define the namespaces we want to use.

After checking the credentials of the user and being sure that they match, we need to create an access token. We will do that by adding the following code right after the verification of the username and password:

$storage = new Pdo(
    $usersTable
        ->adapter
        ->getDriver()
        ->getConnection()
        ->getConnectionParameters()
);
$server = new Server($storage);
$server->addGrantType(new ClientCredentials($storage));
$response = $server->handleTokenRequest(
Request::createFromGlobals()
);
if (!$response->isSuccessful()) {
    $result = new JsonModel(array(
        'result' => false,
        'errors' => 'Invalid oauth'
    ));
}

return new JsonModel($response->getParameters());

Instead of just returning true to the client we need to generate an access token.

First, we will create the PDO storage object using the connection parameters of a table gateway.

Then, we will create the server and specify that we are working with client credentials in order to skip the authorization step.

After that we will call the handleTokenRequest() method on the server by passing a request. This method will take care of generating the access code.

Finally, we will check if everything went fine on the OAuth2.0 side, and we will send a JsonModel object to the client containing the access code, the expiration time, and the token type.

Adding OAuthListener.php to the Common module

Now that we have the authorization mechanism in place, we need to protect the API and check if the access token provided in the request is valid. Of course, there is one endpoint that doesn't need to be protected by the OAuth2.0 protocol.

Now, we are going to create a new listener that will inspect all the requests and check the access token. This will be attached to the dispatch event, which we will see later.

The structure of the file is similar to the ApiErrorListener.php file that we already have, so feel free to copy and paste it and do the following changes:

use OAuth2StoragePdo;
use OAuth2Server;
use OAuth2Request;
use OAuth2Response;

The preceding are lines we need to add to the top of the OAuthListener.php file, because we are going to use these components.

The following are the contents of the attach() method. As you can see, the listener is attached to the dispatch event and will call a method named onDispatch():

public function attach(EventManagerInterface $events)
{
    $this->listeners[] = $events->attach(
        MvcEvent::EVENT_DISPATCH, __CLASS__ . '::onDispatch', 1000
    );
}

Let's take a look at the contents of the onDispatch() method:

if ($e->getRequest() instanceOf endConsoleRequest) {
    return;
}

if ($e->getRouteMatch()->getMatchedRouteName() == 'login' || 
$e->getRouteMatch()->getMatchedRouteName() == 'users') {
    return;
}

$sm = $e->getApplication()->getServiceManager();
$usersTable = $sm->get('UsersModelUsersTable'),

$storage = new Pdo(
    $usersTable
        ->adapter
        ->getDriver()
        ->getConnection()
        ->getConnectionParameters()
);
$server = new Server($storage);
if (!$server->verifyResourceRequest(Request::createFromGlobals)){
    $model = new JsonModel(array(
        'errorCode' => $server->getResponse()->getStatusCode(),
        'errorMsg' => $server->getResponse()->getStatusText()
    ));

    $response = $e->getResponse();
    $response->setContent($model->serialize());
    $response->getHeaders()->addHeaderLine(
        'Content-Type', 'application/json'
    );
    $response->setStatusCode(
        $server->getResponse()->getStatusCode()
    );

    return $response;
}

If you remember, in one of the previous chapters, we created the RSS reader and a CLI interface to process the feeds. This is the reason why the first thing we do here is check that we are not processing a request made through the CLI.

After that, we check the URL that doesn't have to be protected by OAuth 2.0. The route that we don't need to protect is called users. If we protect that endpoint, the user will not be able to log in to the application.

After that, we again create the PDO object and the server component based on the connection parameters.

Then we proceed to verify if the access token is valid for this resource, and this is accomplished by calling the verifyResourceRequest() method on the server component passing the request and response as parameters.

In case of an error, we will return a JSON response to the client specifying the issue and also specifying the 401 HTTP code. Otherwise, we will just allow the request to continue.

Other changes in the Common module

Now, because the request will continue with the ApiErrorListener object, we need to avoid processing those requests marked as 401 by the OAuth 2.0 listener. In order to change that behavior, we need to modify the first check on the onRender() method in the ApiErrorListener.php file. The new check will look as follows:

if ($e->getRequest() instanceOf endConsoleRequest 
    || $e->getResponse()->isOk() 
    || $e->getResponse()
        ->getStatusCode() == Response::STATUS_CODE_401
) {
    return;
}

After making this change, we need to inform the Common module about the new listener we just created. We need to add a listener to the module configuration as follows:

return array(
    'service_manager' =>array(
        'invokables' => array(
            'CommonListenersApiErrorListener' =>
                'CommonListenersApiErrorListener',
            'CommonListenersOAuthListener' =>
                'CommonListenersOAuthListener'
        )
    )
);

As you can see, we just added a new entry specifying the path for the new listener.

Finally, we need to attach the listener to the bootstrap event on the module. This will be done by adding the following line next to the ApiErrorListener initialization on the onBootstrap() method in the Module.php file:

$events->attach($sm->get('CommonListenersOAuthListener'));

This will attach the listener to the dispatch event every time the module is bootstrapped.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset