Login example with Zend_Auth

on January 05, 2009. in Development, Programming. A 6 minute read.

Happy New Year! Hope everyone had a blast for New Year’s Eve and managed to get some rest :) This is my first working day for this year. I’m still kinda lazy and sleepy. And I wanna eat something all the time. Damn you candies!!!

So, here’s what I’m going to do: authenticate an user against a database table using Zend Framework’s Zend_Auth component. It’s really a piece of cake. You can see a working example here: http://robertbasic.com/dev/login/. Feel free to test it and report any misbehavior down in the comments. In the codes below all paths, class names, actions, etc. will be as are in the example, so you probably will need to changed those according to your setup.

Preparation

Because I’m gonna use a database, be sure to have set the default database adapter in the bootstrap file, I have it setup like this:

<?php
$config = new Zend_Config_Ini('../application/dev/config/db_config.ini', 'offline');
$registry = Zend_Registry::getInstance();
$registry->set('db_config',$config);
$db_config = Zend_Registry::get('db_config');
$db = Zend_Db::factory($db_config->db);
Zend_Db_Table::setDefaultAdapter($db);

I’ll need it later in the code. The table structure is as follows:

--
-- Table structure for table `zendLogin`
--

CREATE TABLE `zendLogin` (
  `id` int(11) NOT NULL auto_increment,
  `username` varchar(32) NOT NULL,
  `password` varchar(32) NOT NULL,
  `name` varchar(100) NOT NULL,
  `email` varchar(100) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;

The login controller

The magic happens in the LoginController. It has two actions: indexAction and logoutAction. The indexAction will take care of showing the login form and processing the login process. The logoutAction will just logout the user. You would never figure out that one on your own, right?

Now, let’s get to the fun part — the code:

<?php
class Dev_LoginController extends Zend_Controller_Action
{
    public function indexAction()
    {
        // If we're already logged in, just redirect
        if(Zend_Auth::getInstance()->hasIdentity())
        {
            $this->_redirect('dev/secured/index');
        }

        $request = $this->getRequest();
        $loginForm = $this->getLoginForm();

        $errorMessage = "";

Not much happening here: if the user is already logged in, I don’t want him at the login form, so just redirect him somewhere else; most likely to a home page or a control panel’s index page.

The Zend_Auth implements the Singleton pattern — if you’re not familiar with it read http://framework.zend.com/manual/en/zend.auth.html#zend.auth.introduction and http://www.php.net/manual/en/language.oop5.patterns.php (at php.net scroll down to the example #2).

So, I’m just asking the Zend_Auth does it have an user identity stored in it; the identity gets stored only upon successful log in. I’m also getting the request object. The getLoginForm() is a function that I wrote for assembling the login form and is a part of the LoginController, I’ll show it’s code later.

<?php
if($request->isPost())
{
    if($loginForm->isValid($request->getPost()))
    {
        // get the username and password from the form
        $username = $loginForm->getValue('username');
        $password = $loginForm->getValue('password');

This doesn’t needs a lot of explanation: if it’s a post request, it means the form is submitted. If the submitted data is valid, just get the wanted values from the form.

<?php
        $dbAdapter = Zend_Db_Table::getDefaultAdapter();
        $authAdapter = new Zend_Auth_Adapter_DbTable($dbAdapter);

        $authAdapter->setTableName('zendLogin')
                    ->setIdentityColumn('username')
                    ->setCredentialColumn('password')
                    ->setCredentialTreatment('MD5(?)');

Here I’m getting the default database adapter, so I know whit which database I’m working with. Then I’m creating an adapter for Zend_Auth, which is used for authentication; the docs give good explanation on the adapter, read it here: http://framework.zend.com/manual/en/zend.auth.html#zend.auth.introduction.adapters.

Next, I’m telling the authentication adapter which table to use from the database, and which columns from that table. Also, I’m telling it how to treat the credentials — the passwords are stored as MD5 hashes, so the submitted passwords will first be MD5ed and then checked.

<?php
        // pass to the adapter the submitted username and password
        $authAdapter->setIdentity($username)
                    ->setCredential($password);

        $auth = Zend_Auth::getInstance();
        $result = $auth->authenticate($authAdapter);

I’m passing to the adapter the user submitted username and password, and then trying to authenticate with that username and password.

<?php
        // is the user a valid one?
        if($result->isValid())
        {
            // get all info about this user from the login table
            // ommit only the password, we don't need that
            $userInfo = $authAdapter->getResultRowObject(null, 'password');

            // the default storage is a session with namespace Zend_Auth
            $authStorage = $auth->getStorage();
            $authStorage->write($userInfo);

            $this->_redirect('dev/secured/index');
        }

If the user is successfully authenticated, get all information about him from the table (if any), like the real name, E-mail, etc. I’m leaving out the password, I don’t need that. Next I’m getting the Zend_Auth's default storage and storing in it the user information. In the end I’m redirecting it where I want it.

<?php
else
{
    $errorMessage = "Wrong username or password provided. Please try again.";
}
}
}
$this->view->errorMessage = $errorMessage;
$this->view->loginForm = $loginForm;
}

And this is the end of the indexAction. I know I could take the correct message from $result with getMessages(), but I like more this kind of message, where I’m not telling the user which part did he got wrong.

<?php
public function logoutAction()
{
    // clear everything - session is cleared also!
    Zend_Auth::getInstance()->clearIdentity();
    $this->_redirect('dev/login/index');
}

This is the logoutAction. I’m clearing the identity from Zend_Auth, which is also clearing all data from the Zend_Auth session namespace. And, of course, redirecting back to the login form.

<?php
protected function getLoginForm()
{
    $username = new Zend_Form_Element_Text('username');
    $username->setLabel('Username:')
            ->setRequired(true);

    $password = new Zend_Form_Element_Password('password');
    $password->setLabel('Password:')
            ->setRequired(true);

    $submit = new Zend_Form_Element_Submit('login');
    $submit->setLabel('Login');

    $loginForm = new Zend_Form();
    $loginForm->setAction('/dev/login/index/')
            ->setMethod('post')
            ->addElement($username)
            ->addElement($password)
            ->addElement($submit);

    return $loginForm;
}

As promised, here’s the code for getLoginForm function. That’s the whole LoginController code, not really a rocket science :) Sorry if it’s a bit hard to keep up with the code, I needed it to break it up in smaller pieces…

And here’s the view script for the indexAction.

<?php
<h2>Zend_Login example</h2>

<p>
Hello! This is an example of authenticating users with the Zend Framework...
</p>

<p>Please login to proceed.</p>

<?php if($this->errorMessage != ""): ?>
<p class="error"><?= $this->errorMessage; ?></p>
<?php endif; ?>

<?= $this->loginForm; ?>

Other controllers

Couldn’t come up with a better subtitle :(

Here’s an example how to require the user to log in to see the page: in the init() method ask Zend_Auth is the user logged in, and if not redirect him to the login form. This way the user will have to log in to the “whole controller”. Implement the same only to the indexAction, and the user will have to only log in to see the index page; he’ll be able to access another page without logging in.

<?php
class Dev_SecuredController extends Zend_Controller_Action
{
    function init()
    {
        // if not logged in, redirect to login form
        if(!Zend_Auth::getInstance()->hasIdentity())
        {
            $this->_redirect('dev/login/index');
        }
    }

    public function indexAction()
    {
        // get the user info from the storage (session)
        $userInfo = Zend_Auth::getInstance()->getStorage()->read();

        $this->view->username = $userInfo->username;
        $this->view->name = $userInfo->name;
        $this->view->email = $userInfo->email;
    }

    public function anotherAction()
    {
    }
}

I’m also reading out the user information from the Zend_Auth's storage, that I have stored there during the log in process.

So there. A fully working login system, which can be setup in a really short time.

Update: If you want, you can get an example source code from here: zendLogin.zip ~8kB

Happy hacking!