Zend_Auth best practices
Asked Answered
S

3

5

My goal is to require login for certain pages. I am using Zend Framework MVC, and I'm trying to find examples regarding best practices.

Some notes on what I'm looking for:

  • I want non-logged in users to get a login box, and then return to logged in version of the page, once authenticated
  • I want to use dependency injection, and avoid singletons
  • Small code footprint - tie into Zend mvc structure
  • Should login box be a separate controller and do header redirect? How to return to landing page after auth success? An idea to simply call the login controller action to display the login box in the landing page, or is this a disadvantage regarding search engine indexing?
  • Be able to use external library for handling cookies

Or something completely different. I'm fairly new to the Zend framework, and I want to do it 'the right way'.

Sueannsuede answered 1/4, 2011 at 12:46 Comment(0)
I
4
  • I want non-logged in users to get a login box, and then return to logged in version of the page, once authenticated

Use a FrontController plugin and redirect or forward them to your loginAction.

  • I want to use dependency injection, and avoid singletons

Zend Framework, doesn't currently ship any DI system, however, the Zend_Application_Resource_* actually replace it. What kind of dependency would you need here?

  • Small code footprint - tie into Zend mvc structure

That's up to you.

  • Should login box be a separate controller and do header redirect? How to return to landing page after auth success? An idea to simply call the login controller action to display the login box in the landing page, or is this a disadvantage regarding search engine indexing?

I mostly use a special AuthController with LoginAction & LogoutAction. To redirect the user to the page is was trying to view, I always add a returnUrl element in my forms, and I inject the value of the requested URL to be able to redirect the user, and if none, I redirect him to the index/dashboard, depends.

  • Be able to use external library for handling cookies

Zend_Auth allows you to set your own storage mechanism, so just implement the interface.

$auth = Zend_Auth::getInstance();
$auth->setStorage(new My_Auth_Storage());

But never store authentication result in a cookie, it's so easy to modify it and access your website.

You may also take a look to one of my previous answer.

Imco answered 1/4, 2011 at 13:25 Comment(4)
Regarding needed dependency - specifically, session storage handler. Regarding cookie auth - I have implemented design principles from jaspan.com/improved_persistent_login_cookie_best_practice and I wish to utilize this.Sueannsuede
If you really want to use DI, you can use Symfony DI, which can be integrated well within ZF blog.starreveld.com/2009/11/… However, it may be overhead if you only plan to use it for Auth. You can still write a AuthService and retrieve it by using an Application Resource $this->getInvokeArg('boostrap')->getResource('AuthService'); or by using Zend_Registry.Teriann
How do you populate 'returnUrl' on https pages without referrer?Sueannsuede
You can append a GET parameter in your Auth Front Controller Plugin and redirect to your Auth Controller or You can set it up in $_SESSION but multi opened tab may lead to inconsistent resultTeriann
A
4

You could use the combination of Zend_Auth and Zend_Acl. To extend the other answers I give a short example of how you can manage authentication using zend framework:

First you need to setup a plugin to predispatch all requests and check if the client is allowed to access certain data. This plugin might look like this one:

class Plugin_AccessCheck extends Zend_Controller_Plugin_Abstract {

    private $_acl = null;

    public function __construct(Zend_Acl $acl) {
        $this->_acl = $acl;
    }

    public function preDispatch(Zend_Controller_Request_Abstract $request) {
        //get request information
        $module = $request->getModuleName ();
        $resource = $request->getControllerName ();
        $action = $request->getActionName ();

        try {
            if(!$this->_acl->isAllowed(Zend_Registry::get('role'), 
                                $module . ':' . $resource, $action)){
                $request->setControllerName ('authentication')
                        ->setActionName ('login');
            }
        }catch(Zend_Acl_Exception $e) {
            $request->setControllerName('index')->setActionName ('uups');
        }
    }
}

So every user type has certain permissions that you define in your acl library. On every request you check if the user is allowed to access a resource. If not you redirect to login page, else the preDispatch passes the user to the resource.

In Zend_Acl you define roles, resources and permission, that allow or deny access, e.g.:

class Model_LibraryAcl extends Zend_Acl {
    public function __construct() {

        $this->addRole(new Zend_Acl_Role('guests'));
        $this->addRole(new Zend_Acl_Role('users'), 'guests');
        $this->addRole(new Zend_Acl_Role('admins'), 'users');                

        $this->add(new Zend_Acl_Resource('default'))
             ->add(new Zend_Acl_Resource('default:authentication'), 'default')
             ->add(new Zend_Acl_Resource('default:index'), 'default')
             ->add(new Zend_Acl_Resource('default:error'), 'default');

        $this->allow('guests', 'default:authentication', array('login'));
        $this->allow('guests', 'default:error', 'error');

        $this->allow('users', 'default:authentication', 'logout');          
    }
}

Then you have to setup acl and auth in your bootstrap file:

    private $_acl = null;

    protected function _initAutoload() {

       //...your code           
       if (Zend_Auth::getInstance()->hasIdentity()){
        Zend_Registry::set ('role',
                     Zend_Auth::getInstance()->getStorage()
                                              ->read()
                                              ->role);
        }else{
            Zend_Registry::set('role', 'guests');
        }

        $this->_acl = new Model_LibraryAcl ();
        $fc = Zend_Controller_Front::getInstance ();
        $fc->registerPlugin ( new Plugin_AccessCheck ( $this->_acl ) );

        return $modelLoader;
    }

Finally in your authentication controller you have to use a custom auth adapter and setup actions for login and logout:

public function logoutAction() {
    Zend_Auth::getInstance ()->clearIdentity ();
    $this->_redirect ( 'index/index' );
}

private function getAuthAdapter() {
    $authAdapter = new Zend_Auth_Adapter_DbTable ( 
                        Zend_Db_Table::getDefaultAdapter ());
    $authAdapter->setTableName('users')
                ->setIdentityColumn('email')
                ->setCredentialColumn ('password')
                ->setCredentialTreatment ('SHA1(CONCAT(?,salt))');

    return $authAdapter;
}

In your login action you need to pass login data to the auth adapter which performs the authentication.

$authAdapter = $this->getAuthAdapter ();
$authAdapter->setIdentity ( $username )->setCredential ( $password );
$auth = Zend_Auth::getInstance ();
$result = $auth->authenticate ( $authAdapter );

if ($result->isValid ()) {
    $identity = $authAdapter->getResultRowObject ();
    if ($identity->approved == 'true') {
        $authStorage = $auth->getStorage ();
        $authStorage->write ( $identity );
        $this->_redirect ( 'index/index' );
    } else {
       $this->_redirect ( 'authentication/login' );
  }

And that's all. I recommend you this HOW TO on youtube on zend auth and zend acl.

Alloway answered 2/4, 2011 at 12:15 Comment(1)
This is the most complete anwser I ever seen on SO! +1 For that!Tattler
L
1

Not sure if this is best practice, but if I were to implement what you are after then I'ld do the following:

  1. Create a LoginForm with a uniquely identifiable submit button (explained later)
  2. Create an AuthService with methods getLoginForm, login, logout and one or all of: getIdentity, hasIdentity, and similar methods.
  3. Create a viewhelper that has an instance of this service, and depending on what the outcome of hasIdentity is, either render the LoginForm, or render the identity of logged in user, in the views that need this functionality.
  4. Create a front controller plugin that also has an instance of the service and implement the preDispatch plugin hook. Give it some config of which actions to probe. If AuthService->hasIdentity() returns true, do nothing. If request is a post request look for the uniquely identifiable submit button name, i.e.: $request->getPost( 'loginSubmitButton', null ); defaulting to null if not available. If not null do a login( $request->getPost() ) on the service. If successful, redirect to the same action, if unsuccessful fall through and render the form in the viewhelper again (automatically displaying the errors).

The service could be fetched by both the viewhelper and the front controller plugin with some service locator doing something like ServiceAbstract::getService( 'Auth' ) by registering the service to ServiceAbstract in the bootstrap.

Loveland answered 1/4, 2011 at 13:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.