Sunday, February 3, 2013

zend user login example simple

Central User Authentication using Zend Framework

  Login and Authentication with Zend Framework, is a great article on authentication. All I wanted to add was a bit about the plugin. I have purposely left out discussion of the form and controller because it's well covered in that article.
User authentication is about checking if the user is authentic i.e. the user has a valid identity. Authentication is most commonly done using username (or email) and a password.
When, and how, do you check the user is authenticated? One way is to use a plugin to provide centralised authentication. The plugin can decide if the user should login based on the requested url. As an example, you might have an 'admin' module where you want the user to be logged in before he/she can access the module. Rather than having code to check for this in every controller or action, just add it to a plugin which is automatically called during application boot up.
The plugin in can also be used for user authorisation i.e. determine if the verified users should have access to the resource(s) they are requesting. In combination with the Zend_Acl component, the authorisation levels can be anything you want, including restricting access to a particular action or object e.g. a user can view his profile (access to 'view' action) but not another user's profile (limit access to particular user object).
The great advantage of a plugin is the authentication, and authorisation, code is in one place which helps when you want to refactor or add functionality.

A simple plugin

Here is the code for a simple plugin <?php
/**
 * Centralised user auth
 *
 * @author Paul
 *
 */
class CustomLibrary_Controller_Plugin_Auth extends Zend_Controller_Plugin_Abstract
{
    /**
     * Predispatch method to authenticate user
     *
     * @param Zend_Controller_Request_Abstract $request
     */
    public function preDispatch(Zend_Controller_Request_Abstract $request)
    {
        //user only to login for access to admin functions
        if ('admin' != $request->getModuleName()) {
            return;
        }
       
        if (App_Model_Users::isLoggedIn() && App_Model_Users::isAdmin()) {
            //user is logged in and allowed to access admin functions
            return;
        }
       
        /**
         * User not logged in or not allowed to access admin ... redirect to login.
         * Note: if user is logged in but not authorised, we redirect to login
         * to allow user to login as a different user with the right permissions.
         */
        $request->setModuleName('default')
        ->setControllerName('login')
        ->setActionName('login')
        ->setDispatched(FALSE);
    }
}

The Auth.php plugin file is within the suggested directory structure (see below), but you can have it where you want. Library
 ->CustomLibrary
   ->Controller
     ->Plugin
       ->Auth.php
 ->Zend

The Auth class

In this example I have used a users class model to implement authentication (and simplified authorisation). Here is the code: <?php
/**
 * User model
 *
 * This assumes there is a corresponging table, users, defined as follows:
 *    CREATE TABLE `users` (
 *      `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
 *      `username` VARCHAR(64),
 *      `email` VARCHAR(128) NOT NULL,
 *      `password` VARCHAR(64) NOT NULL,
 *      `salt` VARCHAR(16) NOT NULL,
 *      `status` TINYINT UNSIGNED NOT NULL DEFAULT '1',
 *      `role` ENUM('normal','admin') NOT NULL DEFAULT 'normal',
 *      `fName` VARCHAR(64) NOT NULL,
 *      `lName` VARCHAR(64) NOT NULL,
 *      ...
 *    )
 */
class App_Model_Users
{
    /**
     * Status flags for users in the database
     *
     * @var int
     */
    const STATUS_ACTIVE    = 1;  
    const STATUS_INACTIVE  = 0;
  
    /**
     * Login user
     *
     * @param string $username
     * @param string $password
     * @return bool
     */
    public static function login($username, $password)
    {
        if(!strlen($username) || !strlen($password)) {
            return false;
        }
      
        //site wide password salt
        $staticSalt = Zend_Registry::get('config')->auth->salt;

        /**
        *  Setup Auth Adapter
        *  Note: Zend_Registry::get('db') represents the database adapter you have instantiated elsewhere
        */
      
        $authAdapter = new Zend_Auth_Adapter_DbTable(Zend_Registry::get('db'));
        $authAdapter->setTableName('users')
        ->setIdentityColumn('email') //use email as the username
        ->setCredentialColumn('password')
        ->setIdentity($username)
        ->setCredential($password);
      
        /**
         * The password is a SHA1 hash of the site salt, the password, and
         * the user's salt (in the database). Also only active users can login
         */
        $authAdapter->setCredentialTreatment(
            "SHA1(CONCAT('$staticSalt',?,salt))"
            . " AND status=" . self::STATUS_ACTIVE
        );

        //Authenticate
        $auth = Zend_Auth::getInstance();
        $result = $auth->authenticate($authAdapter);
        if (!$result->isValid()) {
            //failed login
            return false;
        }
      
        //get the matching row and persist to session
        $row = $authAdapter->getResultRowObject(array(
            'id',
            'role',
            'username',
            'lName',
            'fName',
        ));
        $auth->getStorage()->write($row);

        //login successful
        return true;
    }

    /**
     * Logout user
     */
    public function logout()
    {
        //clear auth and user sessions
        if(Zend_Auth::getInstance()->hasIdentity()){
            Zend_Auth::getInstance()->clearIdentity();
            Zend_Session::destroy(true, true);
        }
    }
  

    /**
     * Get the user detail of logged in user
     *
     * @param string $name
     * @return false|string
     */
    public static function getLoggedInUserField($name)
    {
        if(!$name) {
            return false;
        }
       
        //load user auth details
        $user = Zend_Auth::getInstance()->getIdentity();

        //if field is defined in auth identity
        if($user && isset($user->$name)) {
            return $user->$name;
        }

        return false;
    }
  
  
    /**
     *
     * Check if the user is an administrator
     *
     * @return bool
     */
    public static function isAdmin()
    {
        return 'admin' == self::getLoggedInUserField('role');
    }
  
    /**
     * Check if user is logged in
     * @return bool
     */
    public static function isLoggedIn()
    {
        return Zend_Auth::getInstance()->hasIdentity();
    }

}

The login in controller (see Authenticating Users in Zend Framework) will call App_Model_Users::login() to login user. In App_Model_Users::login() we authenticate and, optionally, store custom fields in the auth identity (see Database Table Authentication).

Bootstrap class

The plugin needs to be registered to get our magic to work. You can register this in Bootstrap::run(), or in a resource method as I have done here. Any method in the Bootstrap class which is prefixed by '_init' is considered a resource method, and will be auto-run during boot up. /**
 * Application bootstrap class
 *
 */
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initAppAutoload()
    {
        $autoloader = new Zend_Application_Module_Autoloader(array(
            'namespace' => 'App',
            'basePath' => dirname(__FILE__),
        ));
        return $autoloader;
    }
  
    ...
  
    /**
     *
     * Register plugins
     */
    protected function _initRegisterPlugins()
    {
        $this->bootstrap('Frontcontroller')
        ->getResource('Frontcontroller')
        ->registerPlugin(new CustomLibrary_Controller_Plugin_Auth());
    }

    /**
     * (non-PHPdoc)
     * @see Application/Bootstrap/Zend_Application_Bootstrap_Bootstrap::run()
     */
    public function run()
    {
        Zend_Registry::set('config', new Zend_Config($this->getOptions()));

        //start the sessions
        Zend_Session::start();
      
        ...

        parent::run();
    }
}

No comments:

Post a Comment