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
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
* @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
* @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
* @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
* @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
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
* @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
* @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.
thumbe up for great zf2 tutorial
can U please just post whole project source here pls
Hi,
I am a beginner with Zend framework. For some upcomming projects I would like to use Zend, however there seems to be so little information available on Zend framework 2 for beginners like me.
Would you recommend on using ZF 1 or just go ahead an start with ZF 2?
hi,
I had tried this but having some problem. Could you tell me how to solve it? Thanks!
it’s show:
File:
…\vendor\ZendFramework\library\Zend\Code\Reflection\ParameterReflection.php:66
Message:
Class Zend\Db\Adapter\AbstractAdapter does not exist
Please, can you update your tutorial with zf2 beta4?.. I’m trying it but I have some errors :S
Thank you
Hy!
I think it’s very nice work! Thank you very much!
congratulations!
Istvan Maricsak
Nice tutorial, thanks.
Doesn’t it feel like to get anything done in ZF2 you have to write hundreds of line of code?
I don’t like having to config everything in arrays.
However, I will persevere with ZF2, and this tutial will be very useful
Hi,
This code it is not working for ZF2 Beta 5. Here we go with the changes:
– On Module.php (init and mvcPredispatch functions:
public function init(ModuleManager $moduleManager)
{
$sharedManager = $moduleManager->getEventManager()->getSharedManager();
$sharedManager->attach(‘Zend\Mvc\Application’, ‘dispatch’, array($this, ‘mvcPreDispatch’), 100);
}
public function mvcPreDispatch($event)
{
$di = $event->getTarget()->getServiceManager();
$auth = $di->get(‘User\Event\Authentication’);
return $auth->preDispatch($event);
}
– On Authentication.php (headers changes):
$response->getHeaders()->addHeaders(array(
array(‘Location’ => $url)
));
– On UserController (getlocator changes for login and logout):
$uAuth = $this->getServiceLocator()->get(‘User\Controller\Plugin\UserAuthentication’);
———-
$this->getServiceLocator()->get(‘User\Controller\Plugin\UserAuthentication’)->clearIdentity();
– module.config.php (pluginloader removed and written as follow outside of di item):
‘controller’ => array(
‘map’ => array(
‘userAuthentication’ => ‘User\Controller\Plugin\UserAuthentication’
)
),
I think I’m not forgetting anything. Anyway, thanks for your good aproach :-)
Finaly!
Man… I was looking for a tutorial for days now and I was almost giving up.
Thanks a lot mate.
Hi,
thanks for the great post. I just figured out, how you can directly attach the mvcPreDispatch event to the User\Event\Authentication-class:
Class Module implements BootstrapListenerInterface {
public function onBootstrap(EventInterface $e)
{
$di = $e->getTarget()->getServiceManager();
$auth = $di->get(‘User\Event\Authentication’);
$events = StaticEventManager::getInstance();
$events->attach(‘Zend\Mvc\Controller\ActionController’, ‘dispatch’, array($auth, ‘mvcPreDispatch’), 100);
}
}
Cheers
Hello,
Have you implemented this auth procedure as a module or inside an existing module as Application ?
Sorry If I am being dummy, I just start with Zend performing the “Getting Started with Zend Framework 2” and I want to add authentication for learning and I doubt where to place your try.
Thanks for your time!
Thank`s a lot. Very useful for me.
Excellent tutorial, thanks.
good article
I try to follow the instruction here but it return me this error:
Fatal error: Interface ‘Zend\Module\Consumer\AutoloaderProvider’ not found in /home/content/75/8618275/html/aqcrmdev/module/Application/Module.php on line 35
how can I fix this? by the way I am just new user of zf2 so I am not familiar.
You have to check your own vendor/zendframework/zendframework/library/Zend directory (if you are using the skeleton application). Look for the AutoloaderProvider class. Actually, now we are using the AutoloaderProviderInterface which is located in the Zend/ModuleManager/Feature directory. Replace this with the ‘Zend\Module\Consumer\AutoloaderProvider’ and it should work. Lots of fixing is needed. Keep this idea in your mind and you will be fine.
Hi!
Thanks for this, you’ve saved my brain from total burndown :)
I spent weeks on analyzing the ZfcUser module, but that is a huge mess. Probably the best on market, but too complicated for me to learn the basics.
This Authentication is not only simple but also the best choise to learn from.
Thank you for that!
Hola, intersante Articulo sobre Zend 2 y ACL Login.
Tengo un poco de problema con la Implementacion de tu Articulo en mi projecto en Zend2.
Error: project/module/Application/Module.php(18):
No se si tengo bien configurada la Structura que tu has descrito en tu Articulo. Puedes mopner una descrition mas detallade de la Structura?
Saludos
Pedro J.
Can you explain what consumed Zend_Auth::getInstance Zf1 to Zf2
Where is this code? What github url for this code?
Hello,
first i want to say, this is a nice tutorial / explanation!
But i’m encountering a few problems with it.
First:
The configuration for Zend\Authentication\Adapter\DbTable di always throws an error “Catchable fatal error: Argument 1 passed to Zend\Authentication\Adapter\DbTable::__construct() must be an instance of Zend\Db\Adapter\Adapter, string given in…”.
I found out that “‘zendDb’ => ‘Zend\Db\Adapter\Mysqli'” could not be resolved. But i don’t know why.
Second
“new AuthAdapter()” needs a parameter to be given in getAuthAdapter()-function.
For wich version of the zend framework 2 was this written? I use it on ZendFramework-2.0.6 but, it doesn’t work. :(
Any solution? Used Version of ZF2 would be enough ;)
PS: Sorry for my bad english
hi i’d like to see you the files structure for acl, i’m confused if i can put in a separeted module on include in other module
What you have for your demo please
demo suorce code
The tutorial is great! Just a question: Why you need to use Plugin? I think the UserAuthentication class does not need to implement AbstractPlugin class. I don’t understand which role the Plugin take in this situation
hi
I know basics in zf and when i used this code i got some error
Fatal error: Interface ‘Zend\Module\Consumer\AutoloaderProvider’ not found in C:\xampp\htdocs\propertyladder\module\User\Module.php on line 35
so plz provide me that file