Apigility: Create custom Authentication for Oauth2 with service delegators
Posted in Tutorial PHP, Zend Framework by samsonasik on August 21, 2016
Custom authentication in apigility is do-able with service delegators. We need to wrap
ZF\MvcAuth\Authentication\DefaultAuthenticationListener::class
in decorator. For example, we want to use ZF\OAuth2\Adapter\PdoAdapter
but want to modify checkUserCredentials($username, $password)
to include is_active
check. Let’s do it!
- Setup Apigility Authentication with Oauth2
- With in assumption, we have the following config:
return [ // ... config/autoload/local.php 'zf-oauth2' => [ 'db' => [ 'driver' => 'PDO_Mysql', 'username' => 'root', 'password' => '', 'dsn' => 'mysql:host=localhost;dbname=app_oauth', ], ], // ... ];
We can then modify
config/autoload/zf-mvc-auth-oauth2-override.global.php
as follows:// config/autoload/zf-mvc-auth-oauth2-override.global.php return [ 'service_manager' => [ 'factories' => [ 'ZF\OAuth2\Service\OAuth2Server' => 'Application\MvcAuth\NamedOAuth2ServerFactory', ], ], ];
- Define our own
NamedOAuth2ServerFactory
to use our ownOAuth2ServerFactory
forOAuth2\Server
instance creationnamespace Application\MvcAuth; use Interop\Container\ContainerInterface; class NamedOAuth2ServerFactory { /** * @param ContainerInterface $container * * @return callable */ public function __invoke(ContainerInterface $container) { $config = $container->get('config'); $mvcAuthConfig = isset($config['zf-mvc-auth']['authentication']['adapters']) ? $config['zf-mvc-auth']['authentication']['adapters'] : []; $servers = (object) ['application' => null, 'api' => []]; return function ($type = null) use ( $mvcAuthConfig, $container, $servers ) { foreach ($mvcAuthConfig as $name => $adapterConfig) { if (!isset($adapterConfig['storage']['route'])) { // Not a zf-oauth2 config continue; } if ($type !== $adapterConfig['storage']['route']) { continue; } // Found! return $servers->api[$type] = OAuth2ServerFactory::factory( $adapterConfig['storage'], $container ); } }; } }
- Create our
Application\MvcAuth\OAuth2ServerFactory
based on\ZF\MvcAuth\Factory\OAuth2ServerFactory
namespace Application\MvcAuth; use Interop\Container\ContainerInterface; use OAuth2\GrantType\AuthorizationCode; use OAuth2\GrantType\ClientCredentials; use OAuth2\GrantType\RefreshToken; use OAuth2\GrantType\UserCredentials; use OAuth2\GrantType\JwtBearer; use OAuth2\Server as OAuth2Server; final class OAuth2ServerFactory { private function __construct() { } public static function factory(array $config, ContainerInterface $container) { $allConfig = $container->get('config'); $oauth2Config = isset($allConfig['zf-oauth2']) ? $allConfig['zf-oauth2'] : []; $options = self::marshalOptions($oauth2Config); $oauth2Server = new OAuth2Server( $container->get(\ZF\OAuth2\Adapter\PdoAdapter::class), $options ); return self::injectGrantTypes($oauth2Server, $oauth2Config['grant_types'], $options); } private static function marshalOptions(array $config) { // same as \ZF\MvcAuth\Factory\OAuth2ServerFactory::marshalOptions() } private static function injectGrantTypes( OAuth2Server $server, array $availableGrantTypes, array $options ) { // same as \ZF\MvcAuth\Factory\OAuth2ServerFactory::injectGrantTypes() } }
- As we want custom PdoAdapter, we need to map
\ZF\OAuth2\Adapter\PdoAdapter::class
to ourPdoAdapter
, for example:Application\MvcAuth\PdoAdapter
:namespace Application\MvcAuth; use Zend\Crypt\Bcrypt; use ZF\OAuth2\Adapter\PdoAdapter as BasePdoAdapter; class PdoAdapter extends BasePdoAdapter { public function checkUserCredentials($username, $password) { $stmt = $this->db->prepare( 'SELECT * from oauth_users where username = :username and is_active = 1' ); $stmt->execute(compact('username')); $result = $stmt->fetch(); if ($result === false) { return false; } // bcrypt verify return $this->verifyHash($password, $result['password']); } }
- For our
Application\MvcAuth\PdoAdapter
, we need to define factory for it:namespace Application\MvcAuth; use Interop\Container\ContainerInterface; use ZF\OAuth2\Factory\PdoAdapterFactory as BasePdoAdapterFactory; class PdoAdapterFactory extends BasePdoAdapterFactory { public function __invoke(ContainerInterface $container) { $config = $container->get('config'); return new PdoAdapter([ 'dsn' => $config['zf-oauth2']['db']['dsn'], 'username' => $config['zf-oauth2']['db']['username'], 'password' => $config['zf-oauth2']['db']['password'], 'options' => [], ], []); } }
- Register the adapter into service manager into
config/autoload/global.php
// config/autoload/global.php return [ // ... 'service_manager' => [ 'factories' => [ \ZF\OAuth2\Adapter\PdoAdapter::class => \Application\MvcAuth\PdoAdapterFactory::class, ], ], // ... ];
-
Time to attach the
\ZF\OAuth2\Adapter\PdoAdapter
into our delegated serviceZF\MvcAuth\Authentication\DefaultAuthenticationListener
via delegator factorynamespace Application\MvcAuth; use Interop\Container\ContainerInterface; use OAuth2\Server as OAuth2Server; use Zend\ServiceManager\Factory\DelegatorFactoryInterface; use ZF\MvcAuth\Authentication\OAuth2Adapter; class AuthenticationListenerDelegatorFactory implements DelegatorFactoryInterface { public function __invoke( ContainerInterface $container, $name, callable $callback, array $options = null ) { $listener = call_user_func($callback); $listener->attach( new OAuth2Adapter( new Oauth2Server( $container->get(\ZF\OAuth2\Adapter\PdoAdapter::class), ['Bearer'] ) ) ); return $listener; } }
-
Last one! Register our
AuthenticationListenerDelegatorFactory
into service delegators:// config/autoload/global.php return [ // ... 'service_manager' => [ 'delegators' => [ \ZF\MvcAuth\Authentication\DefaultAuthenticationListener::class => [ \Application\MvcAuth\AuthenticationListenerDelegatorFactory::class ], ], ], // ... ];
Done 😉
13 comments