Zend Framework 2: Authentication + Acl Using EventManager

This is a first try to implement Zend Authentication with Zend Acl under ZF2. I used the EventManager to catch the MVC preDispatch Event so that I can evaluate if the user has or not permission for the requested page.

First I created an extra config for ACL (could be also in module.config.php, but I prefer to have it in a separated file)

  • config/acl.config.php

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    <?php
      return array(
          'acl' => array(
              'roles' => array(
                  'guest'   => null,
                  'member'  => 'guest'
              ),
              'resources' => array(
                  'allow' => array(
                      'user' => array(
                          'login' => 'guest',
                          'all'   => 'member'
                      )
                  )
              )
          )
    );
          
    

    Next step is to create the Acl Class where we manage the roles and resources which we have defined in the config

  • src/User/Acl/Acl.php

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    
    <?php
    /**
     * File for Acl Class
     *
     * @category  User
     * @package   User_Acl
     * @author    Marco Neumann <webcoder_at_binware_dot_org>
     * @copyright Copyright (c) 2011, Marco Neumann
     * @license   http://binware.org/license/index/type:new-bsd New BSD License
     */
    
    /**
     * @namespace
     */
    namespace User\Acl;
    
    /**
     * @uses Zend\Acl\Acl
     * @uses Zend\Acl\Role\GenericRole
     * @uses Zend\Acl\Resource\GenericResource
     */
    use Zend\Acl\Acl as ZendAcl,
        Zend\Acl\Role\GenericRole as Role,
        Zend\Acl\Resource\GenericResource as Resource;
    
    /**
     * Class to handle Acl
     *
     * This class is for loading ACL defined in a config
     *
     * @category User
     * @package  User_Acl
     * @copyright Copyright (c) 2011, Marco Neumann
     * @license   http://binware.org/license/index/type:new-bsd New BSD License
     */
    class Acl extends ZendAcl {
        /**
         * Default Role
         */
        const DEFAULT_ROLE = 'guest';
    
        /**
         * Constructor
         *
         * @param array $config
         * @return void
         * @throws \Exception
         */
        public function __construct($config)
        {
            if (!isset($config['acl']['roles']) || !isset($config['acl']['resources'])) {
                throw new \Exception('Invalid ACL Config found');
            }
    
            $roles = $config['acl']['roles'];
            if (!isset($roles[self::DEFAULT_ROLE])) {
                $roles[self::DEFAULT_ROLE] = '';
            }
    
            $this->_addRoles($roles)
                 ->_addResources($config['acl']['resources']);
        }
    
        /**
         * Adds Roles to ACL
         *
         * @param array $roles
         * @return User\Acl
         */
        protected function _addRoles($roles)
        {
            foreach ($roles as $name => $parent) {
                if (!$this->hasRole($name)) {
                    if (empty($parent)) {
                        $parent = array();
                    } else {
                        $parent = explode(',', $parent);
                    }
    
                    $this->addRole(new Role($name), $parent);
                }
            }
    
            return $this;
        }
    
        /**
         * Adds Resources to ACL
         *
         * @param $resources
         * @return User\Acl
         * @throws \Exception
         */
        protected function _addResources($resources)
        {
            foreach ($resources as $permission => $controllers) {
                foreach ($controllers as $controller => $actions) {
                    if ($controller == 'all') {
                        $controller = null;
                    } else {
                        if (!$this->hasResource($controller)) {
                            $this->addResource(new Resource($controller));
                        }
                    }
    
                    foreach ($actions as $action => $role) {
                        if ($action == 'all') {
                            $action = null;
                        }
    
                        if ($permission == 'allow') {
                            $this->allow($role, $controller, $action);
                        } elseif ($permission == 'deny') {
                            $this->deny($role, $controller, $action);
                        } else {
                            throw new \Exception('No valid permission defined: ' . $permission);
                        }
                    }
                }
            }
    
            return $this;
        }
    }
          
    

    I decided to use a Controller Plugin (zf1 ActionHelpers) for the Authentication interaction

  • src/User/Controller/Plugin/UserAuthentication.php

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    
    <?php
    /**
     * File for UserAuthentication Class
     *
     * @category   User
     * @package    User_Controller
     * @subpackage User_Controller_Plugin
     * @author     Marco Neumann <webcoder_at_binware_dot_org>
     * @copyright  Copyright (c) 2011, Marco Neumann
     * @license    http://binware.org/license/index/type:new-bsd New BSD License
     */
    
    /**
     * @namespace
     */
    namespace User\Controller\Plugin;
    
    /**
     * @uses Zend\Mvc\Controller\Plugin\AbstractPlugin
     * @uses Zend\Authentication\AuthenticationService
     * @uses Zend\Authentication\Adapter\DbTable
     */
    use Zend\Mvc\Controller\Plugin\AbstractPlugin,
        Zend\Authentication\AuthenticationService,
        Zend\Authentication\Adapter\DbTable as AuthAdapter;
    
    /**
     * Class for User Authentication
     *
     * Handles Auth Adapter and Auth Service to check Identity
     *
     * @category   User
     * @package    User_Controller
     * @subpackage User_Controller_Plugin
     * @copyright  Copyright (c) 2011, Marco Neumann
     * @license    http://binware.org/license/index/type:new-bsd New BSD License
     */
    class UserAuthentication extends AbstractPlugin
    {
        /**
         * @var AuthAdapter
         */
        protected $_authAdapter = null;
    
        /**
         * @var AuthenticationService
         */
        protected $_authService = null;
    
        /**
         * Check if Identity is present
         *
         * @return bool
         */
        public function hasIdentity()
        {
            return $this->getAuthService()->hasIdentity();
        }
    
        /**
         * Return current Identity
         *
         * @return mixed|null
         */
        public function getIdentity()
        {
            return $this->getAuthService()->getIdentity();
        }
    
        /**
         * Sets Auth Adapter
         *
         * @param \Zend\Authentication\Adapter\DbTable $authAdapter
         * @return UserAuthentication
         */
        public function setAuthAdapter(AuthAdapter $authAdapter)
        {
            $this->_authAdapter = $authAdapter;
    
            return $this;
        }
    
        /**
         * Returns Auth Adapter
         *
         * @return \Zend\Authentication\Adapter\DbTable
         */
        public function getAuthAdapter()
        {
            if ($this->_authAdapter === null) {
                $this->setAuthAdapter(new AuthAdapter());
            }
    
            return $this->_authAdapter;
        }
    
        /**
         * Sets Auth Service
         *
         * @param \Zend\Authentication\AuthenticationService $authService
         * @return UserAuthentication
         */
        public function setAuthService(AuthenticationService $authService)
        {
            $this->_authService = $authService;
    
            return $this;
        }
    
        /**
         * Gets Auth Service
         *
         * @return \Zend\Authentication\AuthenticationService
         */
        public function getAuthService()
        {
            if ($this->_authService === null) {
                $this->setAuthService(new AuthenticationService());
            }
    
            return $this->_authService;
        }
    
    }
          
    

    Now we can create the Event Handler were we will implement the AuthenticationPlugin (at this point, I think that using a Controller Plugin is not the right way to do it, but for now I will leave it so until I have a better solution…) and the Acl

  • src/Event/Authentication.php

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    
    <?php
    /**
     * File for Event Class
     *
     * @category  User
     * @package   User_Event
     * @author    Marco Neumann <webcoder_at_binware_dot_org>
     * @copyright Copyright (c) 2011, Marco Neumann
     * @license   http://binware.org/license/index/type:new-bsd New BSD License
     */
    
    /**
     * @namespace
     */
    namespace User\Event;
    
    /**
     * @uses Zend\Mvc\MvcEvent
     * @uses User\Controller\Plugin\UserAuthentication
     * @uses User\Acl\Acl
     */
    use Zend\Mvc\MvcEvent as MvcEvent,
        User\Controller\Plugin\UserAuthentication as AuthPlugin,
        User\Acl\Acl as AclClass;
    
    /**
     * Authentication Event Handler Class
     *
     * This Event Handles Authentication
     *
     * @category  User
     * @package   User_Event
     * @copyright Copyright (c) 2011, Marco Neumann
     * @license   http://binware.org/license/index/type:new-bsd New BSD License
     */
    class Authentication
    {
        /**
         * @var AuthPlugin
         */
        protected $_userAuth = null;
    
        /**
         * @var AclClass
         */
        protected $_aclClass = null;
    
        /**
         * preDispatch Event Handler
         *
         * @param \Zend\Mvc\MvcEvent $event
         * @throws \Exception
         */
        public function preDispatch(MvcEvent $event)
        {
            //@todo - Should we really use here and Controller Plugin?
            $userAuth = $this->getUserAuthenticationPlugin();
            $acl = $this->getAclClass();
            $role = AclClass::DEFAULT_ROLE;
    
            if ($userAuth->hasIdentity()) {
                $user = $userAuth->getIdentity();
                $role = 'member'; //@todo - Get role from user!
            }
    
    
            $routeMatch = $event->getRouteMatch();
            $controller = $routeMatch->getParam('controller');
            $action     = $routeMatch->getParam('action');
    
            if (!$acl->hasResource($controller)) {
                throw new \Exception('Resource ' . $controller . ' not defined');
            }
    
            if (!$acl->isAllowed($role, $controller, $action)) {
                $url = $event->getRouter()->assemble(array(), array('name' => 'login'));
                $response = $event->getResponse();
                $response->headers()->addHeaderLine('Location', $url);
                $response->setStatusCode(302);
                $response->sendHeaders();
                exit;
            }
        }
    
        /**
         * Sets Authentication Plugin
         *
         * @param \User\Controller\Plugin\UserAuthentication $userAuthenticationPlugin
         * @return Authentication
         */
        public function setUserAuthenticationPlugin(AuthPlugin $userAuthenticationPlugin)
        {
            $this->_userAuth = $userAuthenticationPlugin;
    
            return $this;
        }
    
        /**
         * Gets Authentication Plugin
         *
         * @return \User\Controller\Plugin\UserAuthentication
         */
        public function getUserAuthenticationPlugin()
        {
            if ($this->_userAuth === null) {
                $this->_userAuth = new AuthPlugin();
            }
    
            return $this->_userAuth;
        }
    
        /**
         * Sets ACL Class
         *
         * @param \User\Acl\Acl $aclClass
         * @return Authentication
         */
        public function setAclClass(AclClass $aclClass)
        {
            $this->_aclClass = $aclClass;
    
            return $this;
        }
    
        /**
         * Gets ACL Class
         *
         * @return \User\Acl\Acl
         */
        public function getAclClass()
        {
            if ($this->_aclClass === null) {
                $this->_aclClass = new AclClass(array());
            }
    
            return $this->_aclClass;
        }
    }
          
    

    Now we need to attach the Event Handler to the EventManager, this will be done in the Module Class (haven’t found a better way to do it). I also had to attach an own function and from there trigger the User\Event\Authentication function (not very elegant ;( )

  • Module.php

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    
    <?php
    /**
     * File for Module Class
     *
     * @category  User
     * @package   User
     * @author    Marco Neumann <webcoder_at_binware_dot_org>
     * @copyright Copyright (c) 2011, Marco Neumann
     * @license   http://binware.org/license/index/type:new-bsd New BSD License
     */
    
    /**
     * @namespace
     */
    namespace User;
    
    /**
     * @uses Zend\Module\Consumer\AutoloaderProvider
     * @uses Zend\EventManager\StaticEventManager
     */
    use Zend\Module\Consumer\AutoloaderProvider,
        Zend\EventManager\StaticEventManager;
    
    /**
     * Module Class
     *
     * Handles Module Initialization
     *
     * @category  User
     * @package   User
     * @copyright Copyright (c) 2011, Marco Neumann
     * @license   http://binware.org/license/index/type:new-bsd New BSD License
     */
    class Module implements AutoloaderProvider
    {
        /**
         * Init function
         *
         * @return void
         */
        public function init()
        {
            // Attach Event to EventManager
            $events = StaticEventManager::getInstance();
            $events->attach('Zend\Mvc\Controller\ActionController', 'dispatch', array($this, 'mvcPreDispatch'), 100); //@todo - Go directly to User\Event\Authentication
        }
    
        /**
         * Get Autoloader Configuration
         *
         * @return array
         */
        public function getAutoloaderConfig()
        {
            return array(
                'Zend\Loader\ClassMapAutoloader' => array(
                    __DIR__ . '/autoload_classmap.php'
                ),
                'Zend\Loader\StandardAutoloader' => array(
                    'namespaces' => array(
                        __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__
                    )
                )
            );
        }
    
        /**
         * Get Module Configuration
         *
         * @return array
         */
        public function getConfig()
        {
            return include __DIR__ . '/config/module.config.php';
        }
    
        /**
         * MVC preDispatch Event
         *
         * @param $event
         * @return mixed
         */
        public function mvcPreDispatch($event) {
            $di = $event->getTarget()->getLocator();
            $auth = $di->get('User\Event\Authentication');
    
            return $auth->preDispatch($event);
        }
    }
          
    

    Here is the config where we attach all needed classes and load acl.config.php

  • config/module.config.php

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    
    <?php
    return array(
        'di' => array(
            'instance' => array(
                'alias' => array(
                    'user' => 'User\Controller\UserController'
                ),
                'user' => array(
                    'parameters' => array(
                        'broker' => 'Zend\Mvc\Controller\PluginBroker'
                    )
                ),
                'User\Event\Authentication' => array(
                    'parameters' => array(
                        'userAuthenticationPlugin' => 'User\Controller\Plugin\UserAuthentication',
                        'aclClass'                 => 'User\Acl\Acl'
                    )
                ),
                'User\Acl\Acl' => array(
                    'parameters' => array(
                        'config' => include __DIR__ . '/acl.config.php'
                    )
                ),
                'User\Controller\Plugin\UserAuthentication' => array(
                    'parameters' => array(
                        'authAdapter' => 'Zend\Authentication\Adapter\DbTable'
                    )
                ),
                'Zend\Authentication\Adapter\DbTable' => array(
                    'parameters' => array(
                        'zendDb' => 'Zend\Db\Adapter\Mysqli',
                        'tableName' => 'users',
                        'identityColumn' => 'email',
                        'credentialColumn' => 'password',
                        'credentialTreatment' => 'SHA1(CONCAT(?, "secretKey"))'
                    )
                ),
                'Zend\Db\Adapter\Mysqli' => array(
                    'parameters' => array(
                        'config' => array(
                            'host' => 'localhost',
                            'username' => 'username',
                            'password' => 'password',
                            'dbname' => 'dbname',
                            'charset' => 'utf-8'
                        )
                    )
                ),
                'Zend\Mvc\Controller\PluginLoader' => array(
                    'parameters' => array(
                        'map' => array(
                            'userAuthentication' => 'User\Controller\Plugin\UserAuthentication'
                        )
                    )
                ),
                'Zend\View\PhpRenderer' => array(
                    'parameters' => array(
                        'options' => array(
                            'script_paths' => array(
                                'user' => __DIR__ . '/../views'
                            )
                        )
                    )
                )
            )
        ),
        'routes' => array(
            'login' => array(
                'type' => 'Zend\Mvc\Router\Http\Literal',
                'options' => array(
                    'route'    => '/login',
                    'defaults' => array(
                        'controller' => 'user',
                        'action'     => 'login',
                    )
                )
            )
        )
    );
          
    

    Well, we are now done, we only need to create the Login Form and the Controller

  • src/Form/Login.php

      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
     19
     20
     21
     22
     23
     24
     25
     26
     27
     28
     29
     30
     31
     32
     33
     34
     35
     36
     37
     38
     39
     40
     41
     42
     43
     44
     45
     46
     47
     48
     49
     50
     51
     52
     53
     54
     55
     56
     57
     58
     59
     60
     61
     62
     63
     64
     65
     66
     67
     68
     69
     70
     71
     72
     73
     74
     75
     76
     77
     78
     79
     80
     81
     82
     83
     84
     85
     86
     87
     88
     89
     90
     91
     92
     93
     94
     95
     96
     97
     98
     99
    100
    
    <?php
    /**
     * File for Login Form Class
     *
     * @category  User
     * @package   User_Form
     * @author    Marco Neumann <webcoder_at_binware_dot_org>
     * @copyright Copyright (c) 2011, Marco Neumann
     * @license   http://binware.org/license/index/type:new-bsd New BSD License
     */
    
    /**
     * @namespace
     */
    namespace User\Form;
    
    /**
     * @uses Zend\Form\Form
     */
    use Zend\Form\Form;
    
    /**
     * Login Form Class
     *
     * Login Form
     *
     * @category  User
     * @package   User_Form
     * @copyright Copyright (c) 2011, Marco Neumann
     * @license   http://binware.org/license/index/type:new-bsd New BSD License
     */
    class Login extends Form
    {
        /**
         * Initialize Form
         */
        public function init()
        {
            $this->setMethod('post')
                 ->loadDefaultDecorators()
                 ->addDecorator('FormErrors');
    
            $this->addElement(
                'text',
                'username',
                array(
                     'filters' => array(
                         array('StringTrim')
                     ),
                     'validators' => array(
                         'EmailAddress'
                     ),
                     'required' => true,
                     'label'    => 'Email'
                )
            );
    
            $this->addElement(
                'password',
                'password',
                array(
                     'filters' => array(
                         array('StringTrim')
                     ),
                     'validators' => array(
                         array(
                             'StringLength',
                             true,
                             array(
                                 6,
                                 999
                             )
                         )
                     ),
                     'required' => true,
                     'label'    => 'Password'
                )
            );
    
            $this->addElement(
                'hash',
                'csrf',
                array(
                     'ignore' => true,
                     'decorators' => array('ViewHelper')
                )
            );
    
            $this->addElement(
                'submit',
                'login',
                array(
                     'ignore' => true,
                     'label' => 'Login'
                )
            );
    
        }
    }
          
    
  • src/User/Controller/UserController.php

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    
    <?php
    /**
     * File for User Controller Class
     *
     * @category  User
     * @package   User_Controller
     * @author    Marco Neumann <webcoder_at_binware_dot_org>
     * @copyright Copyright (c) 2011, Marco Neumann
     * @license   http://binware.org/license/index/type:new-bsd New BSD License
     */
    
    /**
     * @namespace
     */
    namespace User\Controller;
    
    /**
     * @uses Zend\Mvc\Controller\ActionController
     * @uses User\Form\Login
     */
    use Zend\Mvc\Controller\ActionController,
        User\Form\Login as LoginForm;
    
    /**
     * User Controller Class
     *
     * User Controller
     *
     * @category  User
     * @package   User_Controller
     * @copyright Copyright (c) 2011, Marco Neumann
     * @license   http://binware.org/license/index/type:new-bsd New BSD License
     */
    class UserController extends ActionController
    {
        /**
         * Index Action
         */
        public function indexAction()
        {
            //@todo - Implement indexAction
        }
    
        /**
         * Login Action
         *
         * @return array
         */
        public function loginAction()
        {
            $form = new LoginForm();
            $request = $this->getRequest();
    
            if ($request->isPost() && $form->isValid($request->post()->toArray())) {
                $uAuth = $this->getLocator()->get('User\Controller\Plugin\UserAuthentication'); //@todo - We must use PluginLoader $this->userAuthentication()!!
                $authAdapter = $uAuth->getAuthAdapter();
    
                $authAdapter->setIdentity($form->getValue('username'));
                $authAdapter->setCredential($form->getValue('password'));
    
                \Zend\Debug::dump($uAuth->getAuthService()->authenticate($authAdapter));
            }
    
            return array('loginForm' => $form);
        }
    
        /**
         * Logout Action
         */
        public function logoutAction()
        {
            $this->getLocator()->get('User\Controller\Plugin\UserAuthentication')->clearIdentity(); //@todo - We must use PluginLoader $this->userAuthentication()!!
        }
    }
          
    

I know this is not perfect, but this implementation works. For an initial implementation I think it’s ok.


Git Over HTTP (Git-Http-Backend)
How to Root Andy Emulator Under Mac OS X
comments powered by Disqus