Welcome to Abdul Malik Ikhsan's Blog

Create ZF Client Authentication for Apigility Oauth with ApigilityConsumer

Posted in Tutorial PHP, Zend Framework, Zend Framework 2, Zend Framework 3 by samsonasik on March 28, 2017

If you have Apigility as API builder in API side, and client app that consume it using Zend Framework 2/3 or ZF Expressive, you can create an authentication from the client application that call oauth in apigility side.

Zend\Authentication has AbstractAdapter that you can extends to create custom adapter for its need. Let’s assume the applications are like the following diagram:

[CLIENT - A ZF Application]              [API - An Apigility Application]
         |                                          |
   AuthController                     ZF\MvcAuth\Authentication\OAuth2Adapter          
         |                                          |
         |       authenticateAction()               |
         |   ------------------------------------>  |
         |         identity json                    |
         |   <------------------------------------  |

On oauth result call, you may get the following result:

{
  "access_token": "8e4b0e5ddc874a6f1500514ef530dbea3976ae77",
  "expires_in": 3600,
  "token_type": "Bearer",
  "scope": null,
  "refresh_token": "d19b79cd376924409c14ee46e5230617482fb169"
}

The ApigilityConsumer

ApigilityConsumer is a ZF2/ZF3 Apigility Client module (can also be used in ZF Expressive) to consume Apigility API Services.

You can install by run composer command:

composer require samsonasik/apigility-consumer

For full configurations and features, you can read at its README, for this post’s need, you can do something like this:

<?php
// config/autoload/apigility-consumer.local.php
return [
    'apigility-consumer' => [
        // your apigility host url
        'api-host-url' => 'https://your.apigilty.api.host',

        // your apigility oauth setting
        'oauth' => [

            'grant_type'    => 'password',
            'client_id'     => 'your client id',
            'client_secret' => 'your client secret',

        ],

    ],
];

and register the module into config/application.config.php or config/modules.config.php:

<?php
// config/application.config.php or config/modules.config.php
return [
    'ApigilityConsumer', // <-- register here
    'Application',
],

Create Adapter

You need to extends Zend\Authentication\Adapter\AbstractAdapter and implements Zend\Authentication\Adapter\AdapterInterface. So, You can have the class:

<?php

namespace Application\Adapter;

use ApigilityConsumer\Service\ClientAuthService;
use Zend\Authentication\Adapter\AbstractAdapter;
use Zend\Authentication\Adapter\AdapterInterface;
use Zend\Authentication\Result;

class ApigilityAuthenticationAdapter
    extends AbstractAdapter
    implements AdapterInterface
{
    /**
     * @var ClientAuthService
     */
    private $clientAuthService;

    /**
     * @param  ClientAuthService $clientAuthService
     */
    public function __construct(ClientAuthService $clientAuthService)
    {
        $this->clientAuthService = $clientAuthService;
    }

    /**
     * @return Result
     */
    public function authenticate()
    {
        $clientResult = $this->clientAuthService->callAPI(
            [
                // your oauth registered route segment in apigility. 
                'api-route-segment' => '/oauth', 

                'form-data' => [
                    'username' => $this->getIdentity(),
                    'password' => $this->getCredential(),
                ],

                'form-request-method' => 'POST',
            ]
        );

        if (! $clientResult->success) {
            return new Result(Result::FAILURE, null, $clientResult::$messages);
        }

        return new Result(RESULT::SUCCESS, $clientResult->data);
    }
}

Your can now build a factory from it:

<?php
namespace Application\Adapter;

use ApigilityConsumer\Service\ClientAuthService;

class ApigilityAuthenticationAdapterFactory
{
    public function __invoke($container)
    {
        return new ApigilityAuthenticationAdapter(
            $container->get(ClientAuthService::class)
        );
    }
}

You can then register at service_manager:

<?php
// module/Application/config/module.config.php
namespace Application;

'service_manager' => [
    'factories' => [
        // ...
        Adapter\ApigilityAuthenticationAdapter::class => Adapter\ApigilityAuthenticationAdapterFactory::class,
    ],
],

For ZF Expressive, you can register under ‘dependencies’ key.

Set Adapter into AuthenticationService

You need to set authentication service’s adapter with defined adapter above with factory:

<?php
namespace Application\Factory;

use Application\Adapter\ApigilityAuthenticationAdapter;
use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Storage\Session;

class AuthenticationServiceFactory
{
    public function __invoke($container)
    {
        $adapter = $container->get(ApigilityAuthenticationAdapter::class);

        return new AuthenticationService(
            new Session(), // or your own storage implementing  Zend\Authentication\Storage\StorageInterface
            $adapter
        );
    }
}

You can then register also at service_manager:

<?php
// module/Application/config/module.config.php
namespace Application;

use Zend\Authentication\AuthenticationService;

'service_manager' => [
    'factories' => [
        // ...
        AuthenticationService::class => Factory\AuthenticationServiceFactory::class,
    ],
],

For ZF Expressive, you can register under ‘dependencies’ key.

The AuthController::authenticate()

I assume that you already inject controler with login form, use “username” and “password” as field names, and fill the data, so, your AuthController::authenticate() can be like the following:

<?php
namespace Application\Controller;

use Application\Form\LoginForm;
use Zend\Authentication\AuthenticationService;

class AuthController
{
    public function __construct(
        AuthenticationService $authenticationService,
        LoginForm $loginForm,
    ) { /* ...*/ }

    public function authenticateAction()
    {
        /*
         *    check request and form validity here
         */
        $formData = $this->loginForm->getData();
        $this->authenticationService->getAdapter()
                                    ->setIdentity($formData['username'])
                                    ->setCredential($formData['password']);

        $result = $this->authenticationService->authenticate();
        if (!$result->isValid()) {
            /**
             * For security reason, you should not show user the reason of failure,
             * However, if it actually needed for specific purpose, you can pull by call:
             *
             *     $result->getMessages();
             *
             */
            return $this->redirect()->toRoute('/auth');
        }

        return $this->redirect()->toRoute('/account');
    }
}

For ZF Expressive, you can create routed Authentication middleware.

That’s it, you’re now have successfully created a client authentication for your ZF2/ZF3 or ZF Expressive application that consume Apigility oauth.

Tagged with: , ,