Wednesday, May 1, 2013

Zend Authentication and Authorization Tutorial with Zend_Auth and Zend_Acl






Zend Authentication and Authorization Tutorial with Zend_Auth and Zend_Acl

easy zend login Reference site


Here is a short tutorial showing you how to set up Zend_Auth and Zend_Acl in your project. As you know, Zend_Auth deals with the authentication and Zend_Acl deals with the authorization for your application.
The most common scenario is to have the users in a database table, so let’s start by creating the model files for this. The user table will have 4 columns: id, email, password and role. The role column will keep values like ‘user’, ‘admin’, ‘superadmin’ or whatever you need for user separation in your application. This will be used by ACL to differentiate between users who can or not access specific resources.
First we create a Table Data Gateway to connect to our data source:
1
2
3
4
5
6
// application/models/DbTable/User.php
class Application_Model_DbTable_User extends Zend_Db_Table_Abstract
{
    protected $_name = 'user';
    protected $_primary = 'id';
}
After that we need a Data Mapper class for our User objects:
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
// application/models/UserMapper.php
class Application_Model_UserMapper
{
    protected $_dbTable;
    protected function _hydrate($row)
    {
        $user = new Application_Model_User();
        $user->setId($row->id)
             ->setEmail($row->email)
             ->setPassword($row->password)
             ->setRole($row->role);
        return $user;
    }
    public function setDbTable($dbTable)
    {
        if (is_string($dbTable)) {
            $dbTable = new $dbTable();
        }
        if (!$dbTable instanceof Zend_Db_Table_Abstract) {
            throw new Exception('Invalid table data gateway provided');
        }
        $this->_dbTable = $dbTable;
        return $this;
    }
    public function getDbTable()
    {
        if (null === $this->_dbTable) {
            $this->setDbTable('Application_Model_DbTable_User');
        }
        return $this->_dbTable;
    }
    public function save(Application_Model_User $user)
    {
        $data = array(
            'email'       => $user->getEmail(),
            'password'    => $user->getPassword(),
            'role'        => $user->getRole()
        );
        if (null === ($id = $user->getId())) {
            $this->getDbTable()->insert($data);
        } else {
            $this->getDbTable()->update($data, array('id = ?' => $id));
        }
    }
    public function find($id)
    {
        $result = $this->getDbTable()->find($id);
        if (0 == count($result)) {
            return;
        }
        $row = $result->current();
        return $this->_hydrate($row);
    }
    public function fetchAll()
    {
        $resultSet = $this->getDbTable()->fetchAll();
        $entries   = array();
        foreach ($resultSet as $row) {
            $entries[] = $this->_hydrate($row);
        }
        return $entries;
    }
}
And, of course, the User class:
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
// application/models/User.php
class Application_Model_User
{
    protected $_id;
    protected $_email;
    protected $_password;
    protected $_role;
    public function __construct(array $options = null)
    {
        if (is_array($options)) {
            $this->setOptions($options);
        }
    }
    public function setOptions(array $options)
    {
        $methods = get_class_methods($this);
        foreach ($options as $key => $value) {
            $method = 'set' . ucfirst($key);
            if (in_array($method, $methods)) {
                $this->$method($value);
            }
        }
        return $this;
    }
    public function setId($id)
    {
        $this->_id = (int) $id;
        return $this;
    }
    public function getId()
    {
        return $this->_id;
    }
    public function setEmail($email)
    {
        $this->_email = (string) $email;
        return $this;
    }
    public function getEmail()
    {
        return $this->_email;
    }
    public function setPassword($password)
    {
        $this->_password = (string) $password;
        return $this;
    }
    public function getPassword()
    {
        return $this->_password;
    }
    public function setRole($role)
    {
        $this->_role = (string) $role;
        return $this;
    }
    public function getRole()
    {
        return $this->_role;
    }
}
Let’s now create an AuthController in our application that will hold the login and logout actions:
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
// application/controllers/AuthController.php
class AuthController extends Zend_Controller_Action
{
    public function loginAction()
    {
        $users = new Application_Model_DbTable_User();
        $form = new Application_Form_LoginForm();
        $this->view->form = $form;
        if($this->getRequest()->isPost()) {
            if($form->isValid($_POST)) {
                $data = $form->getValues();
                $auth = Zend_Auth::getInstance();
                $authAdapter = new Zend_Auth_Adapter_DbTable($users->getAdapter(), 
'user');
        $authAdapter->setIdentityColumn('email')->setCredentialColumn('password');
               $authAdapter->setIdentity($data['email'])->setCredential($data['password']);
                $result = $auth->authenticate($authAdapter);
                if($result->isValid()) {
                    $storage = new Zend_Auth_Storage_Session();
                    $storage->write($authAdapter->getResultRowObject());
                    $mysession = new Zend_Session_Namespace('mysession');
                    if(isset($mysession->destination_url)) {
                        $url = $mysession->destination_url;
                        unset($mysession->destination_url);
                        $this->_redirect($url);
                    }
                    $this->_redirect('index/index');
                } else {
                    $this->view->errorMessage = "Invalid email or password. Please try again.";
                }
            }
        }
    }
    public function logoutAction()
    {
        $storage = new Zend_Auth_Storage_Session();
        $storage->clear();
        $this->_redirect('index/index');
     }
}
The view for the login action is listed below:
1
2
3
4
5
6
<!-- application/views/scripts/auth/login.phtml -->
<?php if(isset($this->errorMessage)): ?>
    <?php echo $this->errorMessage; ?>
<?php endif; ?>
<?php echo $this->form; ?>
The login form used above is here:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// application/forms/LoginForm.php
class Application_Form_LoginForm extends Zend_Form
{
    public function init()
    {
        $email = $this->createElement('text','email');
        $email->setLabel('Email: *')
              ->setRequired(true);
        $password = $this->createElement('password','password');
        $password->setLabel('Password: *')
                 ->setRequired(true);
        $signin = $this->createElement('submit','signin');
        $signin->setLabel('Sign in')
               ->setIgnore(true);
        $this->addElements(array(
            $email,
            $password,
            $signin,
        ));
    }
}
For the ACL we will use a Front Controller Plugin to secure the entire application:
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
// library/My/Controller/Plugin/ACL.php
class My_Controller_Plugin_ACL extends Zend_Controller_Plugin_Abstract
{
    protected $_defaultRole = 'guest';
    public function preDispatch(Zend_Controller_Request_Abstract $request)
    {
        $auth = Zend_Auth::getInstance();
        $acl = new My_Acl();
        $mysession = new Zend_Session_Namespace('mysession');
        if($auth->hasIdentity()) {
            $user = $auth->getIdentity();
            if(!$acl->isAllowed($user->role, $request->getControllerName() . '::' . $request->getActionName())) {
                $mysession->destination_url = $request->getPathInfo();
                return Zend_Controller_Action_HelperBroker::getStaticHelper('redirector')->setGotoUrl('auth/noauth');
            }
        } else {
            if(!$acl->isAllowed($this->_defaultRole, $request->getControllerName() . '::' . $request->getActionName())) {
                $mysession->destination_url = $request->getPathInfo();
                return Zend_Controller_Action_HelperBroker::getStaticHelper('redirector')->setGotoUrl('auth/login');
            }
        }
    }
}
The Acl class, that will define resources and levels of authorization, is next:
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
// library/My/Acl.php
class My_Acl extends Zend_Acl
{
    public function __construct()
    {
        // Add a new role called "guest"
        $this->addRole(new Zend_Acl_Role('guest'));
        // Add a role called user, which inherits from guest
        $this->addRole(new Zend_Acl_Role('user'), 'guest');
        // Add a role called admin, which inherits from user
        $this->addRole(new Zend_Acl_Role('admin'), 'user');
        // Add some resources in the form controller::action
        $this->add(new Zend_Acl_Resource('error::error'));
        $this->add(new Zend_Acl_Resource('auth::login'));
        $this->add(new Zend_Acl_Resource('auth::logout'));
        $this->add(new Zend_Acl_Resource('index::index'));
        // Allow guests to see the error, login and index pages
        $this->allow('guest', 'error::error');
        $this->allow('guest', 'auth::login');
        $this->allow('guest', 'index::index');
        // Allow users to access logout and the index action from the user controller
        $this->allow('user', 'auth::logout');
        $this->allow('user', 'user::index');
        // Allow admin to access admin controller, index action
        $this->allow('user', 'admin::index');
        // You will add here roles, resources and authorization specific to your application, the above are some examples
    }
}
Now, to enable the plugin we created, we need to add to the application bootstrap file the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
// application/Bootstrap.php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initPlugins()
    {
        $autoloader = Zend_Loader_Autoloader::getInstance();
        $autoloader->registerNamespace('My_');
        $objFront = Zend_Controller_Front::getInstance();
        $objFront->registerPlugin(new My_Controller_Plugin_ACL(), 1);
        return $objFront;
    }
}
After this, every time a user access you application, the ACL will check the role of the user and send him to the login form if he is not authenticated or to the auth/noauth page if he has no right seeing that page.

simple mysql and php prodcuts and cart page example


create a mysql connect file and include here
and create table using this query

products table:
------------------------------------
    CREATE TABLE `php_shop_products` (
        `id` INT NOT NULL AUTO_INCREMENT ,
        `name` VARCHAR( 255 ) NOT NULL ,
        `description` TEXT,
        `price` DOUBLE DEFAULT '0.00' NOT NULL ,
        PRIMARY KEY ( `id` )
    );
Step->create config.php add the connect string
<?php
$con=mysql_connect('localhost','root','');
mysql_select_db('test',$con);

?>

create products.php page
<?php require_once('config.php') ;?>
<table border="1">


    <?php
       
        $sql = "SELECT id, name, description, price FROM php_shop_products;";
       
        $result = mysql_query($sql);
       
        while(list($id, $name, $description, $price) = mysql_fetch_row($result)) {
       
            echo "<tr>";
           
                echo "<td>$name</td>";
                echo "<td>$description</td>";
                echo "<td>$price</td>";
                echo "<td><a href=\"cart.php?action=add&id=$id\">Add To Cart</a></td>";
           
            echo "</tr>";
        }
       
    ?>
</table>


<a href="cart.php">View Cart</a>

-------------------------------------------------------------------------------------------------
create cart.php
<?php require_once('config.php') ;?>
<?php

    $product_id = $_GET['id'];     //the product id from the URL
    $action     = $_GET['action']; //the action from the URL

    //if there is an product_id and that product_id doesn't exist display an error message
    if($product_id && !productExists($product_id)) {
        die("Error. Product Doesn't Exist");
    }

    switch($action) {    //decide what to do   
   
        case "add":
            $_SESSION['cart'][$product_id]++; //add one to the quantity of the product with id $product_id
        break;
       
        case "remove":
            $_SESSION['cart'][$product_id]--; //remove one from the quantity of the product with id $product_id
            if($_SESSION['cart'][$product_id] == 0) unset($_SESSION['cart'][$product_id]); //if the quantity is zero, remove it completely (using the 'unset' function) - otherwise is will show zero, then -1, -2 etc when the user keeps removing items.
        break;
       
        case "empty":
            unset($_SESSION['cart']); //unset the whole cart, i.e. empty the cart.
        break;
   
    }
   
?>


<?php   

    if($_SESSION['cart']) {    //if the cart isn't empty
        //show the cart
       
        echo "<table border=\"1\" padding=\"3\" width=\"40%\">";    //format the cart using a HTML table
       
            //iterate through the cart, the $product_id is the key and $quantity is the value
            foreach($_SESSION['cart'] as $product_id => $quantity) {   
               
                //get the name, description and price from the database - this will depend on your database implementation.
                //use sprintf to make sure that $product_id is inserted into the query as a number - to prevent SQL injection
                $sql = sprintf("SELECT name, description, price FROM php_shop_products WHERE id = %d;",
                                $product_id);
                   
                $result = mysql_query($sql);
                   
                //Only display the row if there is a product (though there should always be as we have already checked)
                if(mysql_num_rows($result) > 0) {
               
                    list($name, $description, $price) = mysql_fetch_row($result);
               
                    $line_cost = $price * $quantity;        //work out the line cost
                    $total = $total + $line_cost;            //add to the total cost
               
                    echo "<tr>";
                        //show this information in table cells
                        echo "<td align=\"center\">$name</td>";
                        //along with a 'remove' link next to the quantity - which links to this page, but with an action of remove, and the id of the current product
                        echo "<td align=\"center\">$quantity <a href=\"$_SERVER[PHP_SELF]?action=remove&id=$product_id\">X</a></td>";
                        echo "<td align=\"center\">$line_cost</td>";
                   
                    echo "</tr>";
                   
                }
           
            }
           
            //show the total
            echo "<tr>";
                echo "<td colspan=\"2\" align=\"right\">Total</td>";
                echo "<td align=\"right\">$total</td>";
            echo "</tr>";
           
            //show the empty cart link - which links to this page, but with an action of empty. A simple bit of javascript in the onlick event of the link asks the user for confirmation
            echo "<tr>";
                echo "<td colspan=\"3\" align=\"right\"><a href=\"$_SERVER[PHP_SELF]?action=empty\" onclick=\"return confirm('Are you sure?');\">Empty Cart</a></td>";
            echo "</tr>";       
        echo "</table>";
       
       
   
    }else{
        //otherwise tell the user they have no items in their cart
        echo "You have no items in your shopping cart.";
       
    }
   
    //function to check if a product exists
    function productExists($product_id) {
            //use sprintf to make sure that $product_id is inserted into the query as a number - to prevent SQL injection
            $sql = sprintf("SELECT * FROM php_shop_products WHERE id = %d;",
                            $product_id);
               
            return mysql_num_rows(mysql_query($sql)) > 0;
    }
?>

<a href="products.php">Continue Shopping</a>





AttachmentSize
cart.php4.44 KB
products.php1.