This took a while to figure out, so I’m going to write an entry on it.
Over the last few days, I’ve been working with the Zend_Auth, Zend_Ldap and Zend_Acl classes in the Zend Framework while developing a login framework. I’ve created custom login frameworks before, using php, coldfusion and java. But this time I wanted to make the most use of these classes, without writing a lot of extra, custom code. I figured that the Zend Framework classes would take care of most everything if I just learned how to use all the features they provide.
Setup
First thing I needed was to create a basic login form. I used the Zend_Form for this, and created it inside of a LoginController class. The LoginController class is a basic controller class that extends Zend_Controller_Action. It uses a login action, which calls a function that handles all of the Ldap connections. In the end, I came up with the following class that sits in my /application/controllers/LoginController.php directory.
<?php class LoginController extends Zend_Controller_Action { private $authenticationErrors = array(); public function init() { Zend_Loader::loadClass('Zend_Form'); Zend_Loader::loadClass('Zend_Form_Element'); Zend_Loader::loadClass('Zend_Form_Element_Text'); Zend_Loader::loadClass('Zend_Form_Element_Submit'); Zend_Loader::loadClass('Zend_Form_Element_Password'); Zend_Loader::loadClass('Zend_Form_Decorator_ViewHelper'); Zend_Loader::loadClass('Zend_Form_Decorator_Label'); Zend_Loader::loadClass('Zend_Validate_Alnum'); //only for extending the class constructor and initializing resources } //init public function indexAction() { $request = $this->getRequest(); $request->setControllerName('login') ->setActionName('login') ->setDispatched(false); } //indexAction private function authenticate($credentials) { return $this->authenticateLdap($credentials); } //authenticate public function loginAction() { $request = $this->getRequest(); $username = new Zend_Form_Element_Text('username'); $username->setLabel('Username:'); $username->setRequired(true); $usernameValidator = new Zend_Validate_Alnum(); $username->addValidator($usernameValidator); $password = new Zend_Form_Element_Password('password'); $password->setLabel('Password:'); $password->setRequired(true); $submit = new Zend_Form_Element_Submit('submit'); $submit->setLabel('Login'); $loginForm = new Zend_Form(); $loginForm->setAction('') ->setMethod('post') ->setAttrib('id','loginform'); $loginForm->addElement($username); $loginForm->addElement($password); $loginForm->addElement($submit); if($request->isPost()) { $formData = $request->getPost(); if($loginForm->isValid($formData)) { $credentials = new stdclass(); $credentials->username = $formData['username']; $credentials->password = $formData['password']; if($this->authenticate($credentials)) { $request->setControllerName('user') ->setActionName('profile') ->setDispatched(false); } else { $this->view->authenticationErrors = $this->authenticationErrors; $this->view->errorContainer = $this->view->partial('partial/partial-message-login-error.phtml', $this->view); } } else { $loginForm->populate($formData); } } $loginForm->render(); $this->view->loginForm = $loginForm; } //loginAction public function logoutAction() { $request = $this->getRequest(); $auth = Zend_Auth::getInstance(); $auth->clearIdentity(); $request->setControllerName('login') ->setActionName('login') ->setDispatched(false); } //logoutAction private function authenticateLdap($credentials) { $auth = Zend_Auth::getInstance(); $auth->clearIdentity(); //clear out any existing login information $adapter = new Zend_Auth_Adapter_Ldap( Zend_Registry::get('config')->ldap->toArray(), $credentials->username, $credentials->password ); $result = $auth->authenticate($adapter); $ldap = $adapter->getLdap(); $messages = $result->getMessages(); array_push($this->authenticationErrors,array('messageType'=>'Error', 'messageText'=>$messages[0])); if($auth->hasIdentity()) { $ldap = $adapter->getLdap(); $ldap->bind($credentials->username,$credentials->password); $entry = $ldap->getEntry($ldap->getCanonicalAccountName($credentials->username, Zend_Ldap::ACCTNAME_FORM_DN)); $roles = $entry['memberof']; $identity = new stdclass(); $identity->username = $credentials->username; $identity->roles = $roles; $auth->getStorage()->write($identity); } return $auth->hasIdentity(); } //authenticateLdap } //LoginController
A lot of the LDAP configuration is coming from the config.ini file (/application/config/config.ini). If you want to see more details on how to configure the config.ini file to use it with Active Directory, click here to see the post.
Explained
Here’s what the login form looks like when rendered. It uses a view; login.phtml; to render itself to the browser.
Login Form
If I click “Login” without entering any values, these are the errors that will appear:
If I try to submit credentials that don’t exist in Active Directory, the form will display an error message above it that only appears if there are some error message to display:
This is the view code that displays the form (located in the /application/views/scripts/login/login.phtml folder. It doesn’t have much to it, and leaves most of the processing for the action controller.
<?= $this->errorContainer; ?> <div id="logincontainer"> <?= $this->loginForm; ?> </div>
Again, the $this->errorContainer only displays when it has data in it. If there aren’t any errors, the loginAction() function will leave this value as null.