Friday, March 28. 2008Login and Authentication with Zend FrameworkUpdate: this article is now available in French, courtesy of Frédéric Blanc. I've fielded a number of questions from people wanting to know how to handle authentication and identity persistence in Zend Framework. The typical issue is that they're unsure how to combine:
It's not terribly difficult, but it does require knowing how the various pieces of the MVC fit together, and how to use Zend_Auth. Let's take a look. Authentication AdapterFor all this to work, you'll need an authentication adapter. I'm not going to go into specifics on this, as the documentation covers them, and your needs will vary based on your site. I will make the assumption, however, that your authentication adapter requires a username and password for authentication credentials. Our login controller will make use of the adapter, but simply have a placeholder for retrieving it. Login FormThe login form itself is pretty simple. You can setup some basic validation rules so that you can prevent a database or other service hit, but otherwise keep things relatively simple. For purposes of this tutorial, we'll define the following criteria:
The form would look like this: class LoginForm extends Zend_Form { public function init() { $username = $this->addElement('text', 'username', array( 'filters' => array('StringTrim', 'StringToLower'), 'validators' => array( 'Alpha', array('StringLength', false, array(3, 20)), ), 'required' => true, 'label' => 'Your username:', )); $password = $this->addElement('password', 'password', array( 'filters' => array('StringTrim'), 'validators' => array( 'Alnum', array('StringLength', false, array(6, 20)), ), 'required' => true, 'label' => 'Password:', )); $login = $this->addElement('submit', 'login', array( 'required' => false, 'ignore' => true, 'label' => 'Login', )); // We want to display a 'failed authentication' message if necessary; // we'll do that with the form 'description', so we need to add that // decorator. $this->setDecorators(array( 'FormElements', array('HtmlTag', array('tag' => 'dl', 'class' => 'zend_form')), array('Description', array('placement' => 'prepend')), 'Form' )); } } Login ControllerNow, let's create a controller for handling login and logout actions. The typical flow would be:
The LoginController will make use of your chosen authentication adapter, as well as the login form. We will pass to the login form constructor the form action and method (since we now know what they will be for this usage of the form). When we have valid values, we'll pass them to our authentication adapter. So, let's create the controller. First off, we'll create accessors for the form and authentication adapter. class LoginController extends Zend_Controller_Action { public function getForm() { return new LoginForm(array( 'action' => '/login/process', 'method' => 'post', )); } public function getAuthAdapter(array $params) { // Leaving this to the developer... // Makes the assumption that the constructor takes an array of // parameters which it then uses as credentials to verify identity. // Our form, of course, will just pass the parameters 'username' // and 'password'. } } Next, we need to do some checking before we dispatch any actions to ensure the following:
The following class LoginController extends Zend_Controller_Action { // ... public function preDispatch() { if (Zend_Auth::getInstance()->hasIdentity()) { // If the user is logged in, we don't want to show the login form; // however, the logout action should still be available if ('logout' != $this->getRequest()->getActionName()) { $this->_helper->redirector('index', 'index'); } } else { // If they aren't, they can't logout, so that action should // redirect to the login form if ('logout' == $this->getRequest()->getActionName()) { $this->_helper->redirector('index'); } } } } Now, we need to do our login form. This is our simplest method -- we simply retrieve the form and assign it to the view: class LoginController extends Zend_Controller_Action { // ... public function indexAction() { $this->view->form = $this->getForm(); } } Processing the form involves slightly more logic. We need to verify that we have a post request, then that the form is valid, and finally that the credentials are valid. class LoginController extends Zend_Controller_Action { // ... public function processAction() { $request = $this->getRequest(); // Check if we have a POST request if (!$request->isPost()) { return $this->_helper->redirector('index'); } // Get our form and validate it $form = $this->getForm(); if (!$form->isValid($request->getPost())) { // Invalid entries $this->view->form = $form; return $this->render('index'); // re-render the login form } // Get our authentication adapter and check credentials $adapter = $this->getAuthAdapter($form->getValues()); $auth = Zend_Auth::getInstance(); $result = $auth->authenticate($adapter); if (!$result->isValid()) { // Invalid credentials $form->setDescription('Invalid credentials provided'); $this->view->form = $form; return $this->render('index'); // re-render the login form } // We're authenticated! Redirect to the home page $this->_helper->redirector('index', 'index'); } } Finally, we can tackle the logout action. This is almost as simple as displaying the login form; we simply clear the identity from the authentication object, and redirect: class LoginController extends Zend_Controller_Action { // ... public function logoutAction() { Zend_Auth::getInstance()->clearIdentity(); $this->_helper->redirector('index'); // back to login page } } Okay, that's it for our login/logout routines. Let's look at the one associated view we have, the form: <? // login/index.phtml ?> <h2>Please Login</h2> <?= $this->form ?>
And that's it. Really. Zend_Form makes view scripts simple. Checking for Authenticated UsersThe last part of the question area is: how do I determine if a user is authenticated, and restrict access if not?
If you look carefully at the Zend_Auth::getInstance()->hasIdentity() You can use this to determine if the user is logged in, and then use the redirector to redirect to the login page if not. You can pull the identity from the auth object as well: $identity = Zend_Auth::getInstance()->getIdentity(); This could be sprinkled into a helper to show login status in your layout, for instance: /** * ProfileLink helper * * Call as $this->profileLink() in your layout script */ class My_View_Helper_ProfileLink { public $view; public function setView(Zend_View_Interface $view) { $this->view = $view; } public function profileLink() { $auth = Zend_Auth::getInstance(); if ($auth->hasIdentity()) { $username = $auth->getIdentity()->username; return '<a href="/profile' . $username . '">Welcome, ' . $username . '</a>'; } return '<a href="/login">Login</a>'; } } Conclusion
Comments
Display comments as
(Linear | Threaded)
Hey Matthew,
this seems to be a very helpful tutorial. I think I will try this today and have some fun with ZF. Thank you for your great work for ZF and your nice character within mailing list. First of all I'd like to say thanks for your contributions to the ZF, I'm loving it so far.
I'm currently in the process of building a prototype using several pieces of the ZF, although it's not currently using Zend_Auth. I'm authenticating against SOAP web service, which I suppose could be implemented with an Auth_Adapter, but I haven't gone down that road yet. What I did do is create a Controller_Plugin which listens for a login (by checking the POST data) and handles it appropriately; or if no login is provided checks if the user is authenticated and handles that response properly. This makes it so the user can get linked to http://example.com/wherever/they/want and if they are not logged in I set the route to the login page, where the form simply posts back to itself so the user will login and automatically be where they tried to get in the first place. This is my first whack at the ZF... but I'm very much looking forward into getting to know it more intimately. The MVC+Layout, OpenID, and Translate pieces are phenomenal. Thanks again, and keep up the good work! If I'm able to push my prototype into production hopefully I can provide a case study for you guys or something.... I'll be going to battle with a home-baked Java framework, so wish me luck couple of syntax errors
array('StringLength', false, array(3, 20), array('StringLength', false, array(6, 20), missing extra ) should be array('StringLength', false, array(3, 20)), array('StringLength', false, array(6, 20)), Fatal error: Cannot use object of type stdClass as array in C:\wamp\www\bigwoolly\application\models\Stores.php on line 54
Excellent article! I appreciate all the hard work you have put into ZF.
Matthew Hello, thank you for this tutorial.
I have taken the liberty of translating it into Spanish, I hope not to create any problems. feedback: http://cuatroxl.wordpress.com/2008/03/29/login-y-autentificacion-con-zend-framework/ Hello Matthew,
Thanks for taking the time to write this, I've been itching to dive into Zend_Auth and I can see a lot of value with this sample. Also, I like how Zend_Form lends itself to encapsulating forms outside of the view, giving them a lot more portability. Great article Matt, looking forward to more.
Just a quick note. I think the LoginForm init() function needs to be called. Perhaps __construct() { parent::__construct(); $this->init(); } Or am I just missing something? init() was added in 1.5.1, and is called in __construct() prior to loading the default decorators.
Excellent post. There's just one thing here that I must point out... at least in the past, when I've authenticated users with $auth->authenticate($adapter), it seems zend_auth sticks the user's name in the identity *even if the auth fails*
This meant using hasIdentity does not work like expected and you'd have to clearIdentity after the failed auth attempt. Not sure if this happens in the latest version of ZF, though. Jani -- thanks for the tip. I haven't experienced that behavior yet, but I'll keep my eye out for it (and hopefully fix it, if it rears its head).
Hello Matthew,
The problem I currently have, is to implement authentication via a form that is just a part of a layout. I think i'm stuck there. It is not a problem to display a form via a controller action like you did in your example above but to integrate it into a layout and always display it if not authenticated. if the user then submits the form instead of the form a menu like Profile,.... etc should appear. The problem especially is that i need to render everything to a layout key to place it to the right place. Maybe you can advice something? How can i ensure that the appropiate layout keys get set on every page? Thanks for the post, Matthew.
The simple data persistence has it's disadvantages as the persisted data does not necessarily reflect the actual database row in it's current state (in case of database authentication of course). I know that there is the possibility of calling write() when there are changes, but this won't work when row data is externally updated by maintenance scripts etc. I have not yet found a way to force Zend_Auth to always update the persisted data on each request by fetching the current row. Any help/advice where to implement such functionality would be greatly appreciated. Nice walk through!
Maybe I'm missing something, but how could: if(!$auth->isValid($adapter)) work? Shouldn't that be $auth->authenticate($adapter); ?? I can see the comment above regarding the problems of using authenticate(), but is isValid() really the solution? Best, //Anders Or maybe it should be:
$result = $adapter->authenticate(); if($result->isValid()) ? //Anders Yes, you're right -- and that's how I have it in my code, but I had a bad copy and paste; I've fixed it now.
You forgot a semi column and it I think it should be
$result = $auth->authenticate($adapter); if ($result->isValid() === false) { } Fixed -- thanks. (where did my test first mantra go...?)
The code in public function processAction() still shows the uncorrected way, I think you forgot to update something
#12.1.1.1.1
Arnaud
on
2008-04-01 03:06
(Reply)
Check now -- I believe it's fixed once and for all.
what about checking user-login on the Plugin?
in most applications, you would want to restrict pages instead of just showing the "profile link" of the currently logged in user. i would have a plugin that looks something like this: public function preDispatch(Zend_Controller_Request_Abstract $request) { try { $controllerName = $this->getRequest()->getControllerName(); $frontController = Zend_Controller_Front::getInstance(); $user = Zend_Auth::getInstance()->getIdentity(); if ($controllerName=='index' OR $controllerName=='auth') { return true; } if(!isset($user)) { throw new Exception(); } } catch (Exception $e) { $this->getResponse()->setHttpResponseCode(403); $request->setControllerName('error'); $request->setActionName('noaccess'); } } } one could do additional ACL checking within the plugin. the great thing about using a plugin is that whenever you need to make changes with your ACL (or something), all you have to do is to edit the plugin. this is really helpful for me because i used to put a function in the preDispatch() of every Controller to check user-login. if i need to make changes, i would have to edit all of the controllers... thanks again for the post Matthew! I specifically chose not to address ACLs in this tutorial to keep it simple and to the point: how do you authenticate a user and persist their identity afterwards. I then show techniques on how to retrieve the identity later -- which could be used from within a plugin to check ACLs for the user.
Yes, ACLs *are* typically best done from a plugin, but that's a topic for another day. Thanks!
Hi all,
I'm new to Zend Framework and I'm wondering what the difference is between the redirector calls in the following code: if ('logout' != $this->getRequest()->getActionName()) { $this->_helper->redirector('index', 'index'); } } else { // If they aren't, they can't logout, so that action should // redirect to the login form if ('logout' == $this->getRequest()->getActionName()) { $this->_helper->redirector('index'); } One redirector points to 'index', 'index' and one to 'index'. Can someone explain me what the result will be and how this works? I've checked the manual (http://framework.zend.com/manual/en/zend.controller.actionhelpers.html) but I can't find any information about the constructor and the arguments. The Redirector helper, when called as a method of the helper broker, proxies to its own goto() method. That method mimics the signature of Zend_Controller_Action::_forward(), which accepts the params: $action, $controller, $module, array $params. If no $controller is provided, the current controller is assumed; if no module is provided, the current module is assumed.
So, in the case where I call ('index', 'index'), I'm directing to the home page (this will actually be rewritten as '/' during redirection); in the case of ('index'), I'm redirecting to the index action of the current controller -- LoginController (internally, this will be rewritten to '/login'). The Redirector also has a way for you to specify a named route and pass parameters, in case you need the URL to follow a specific, custom schema. Thanks for your explanation Matthew! It's no totally clear to me how it works.
Hello Matthew,
First I like to thank you very much for posting this. I am very new to Zend Framework moving over from CodeIgnitor. I have two requests 1) Can you provide us with the file and folder structure? 2) It seams the only thing missing to make this a working example is the db. Could you use an array with key username and value password? Something like $users = array('matthew'=>'1234', 'tanya'=>'4321', 'john'=>'5678', 'troy'=>'8765'); Best would be a working example of this in a zip file or google svn using sqlile as the db. You know what if you do get me going by providing the above I will create the svn for you, which ever you prefer. How is that? I use a standard ZF directory structure, which is outlined in our MVC docs as well as in the new Quick Start guide. As for the database, I've typically used Zend_Auth_Adapter_DbTable, and there is excellent documentation in the manual showing how to setup and utilize a database table for use with that adapter.
You have a class "LoginForm" in your code. Where do you put this class within the standard ZF directory structure you have?
That's one thing I really wondered when going through this excellent tutorial. Thanks! Hi Matthew,
thanks for an other great tutorial ! While trying it out for my own application I noticed a problem with the code for the view helper. In the method profileLink() you are using $username = $auth->getIdentity()->username; this gives a "Trying to get property of non-object" error. I looked at the code and $auth->getIdentity() returns an array. So, unless I missed something, shouldn't it be something like: $identity = $auth->getIdentity(); $username = $identity['username']; $auth->getIdentity() in most cases should be returning a stdClass object -- but this may vary based on the authentication adapter you utilize. The Zend_Auth_Adapter_DbTable adapter definitely returns a stdClass, and that's the adapter I'm most familiar with.
Thanks for the clarifications. I'm using Zend_Auth_Adapter_Digest so it must be why. Although, I'm digging through the code but can't find where the stdClass Object is set up when using Zend_Auth_Adapter_DbTable.
In fact when Zend_Auth_Adapter_DbTable is used as adapter $auth->getIdentity() returns string (assuming $auth is an instance of Zend_Auth).
HTTP adapter returns array and Digest adapter returns array. I can't find any Zend_Auth_Adapter_* class shipped with zend framework to set stdClass object in the $identity parameter of Zend_Auth_Result constructor when returning it. Excellent tutorial. A great starting point for new adopters like me.
A question: How would you display a message to the user that their login failed due to bad credentials? You'll notice that I add an additional decorator to the form, 'Description'. That decorator displays an HTML div with the contents provided by setDescription()... if any contents have been provided.
In processAction(), if the form is invalid, I call setDescription() indicating that the credentials are invalid. Thus, when the form is re-displayed, this message will be provided to the user. Took me while but after searching for Zend Framework and authentication etc I landed on this perfect cut'n'paste page
Also, NEW to ZF and just need the last missing piece to start reviewing the code and understand the structure of this. So, above there is a gap when calling the getAuthAdapter. What should this return ? My plan is to (within a year or so Thank you, Bjorn I appreciate this tutorial but am have problems getting this working. Being new to ZF, I started off with the "Hello World" example in the manual using the IndexController, with a few Auth and ACL examples thrown in. So far so good - I was now ready to try the Forms part with your example.
My directory structure is according to the manual I think, so I put the code you listed above in ../application/views/scripts/login/login.phtml ../application/controllers/LoginController.php and in /html is index.php and LoginForm.php. Would that be be correct? URL rewriting seems ok, since the URLs ending in /index and /index/index do call call the IndexController via Zend_Controller_Front::run('../application/controllers'); The URLS /login though, gives no ZF error, just an error about not finding /index.php in the browser. The .htaccess has the usual: RewriteRule !\.(js|ico|gif|jpg|png|css)$ index.php Thx in advance. LoginForm.php should be somewhere in your include_path; it would have been better for me to name it something like 'My/Form/Login.php', and indicate that this would be found in your include_path.
Also, please notice that the view script should be in application/views/scripts/login/index.phtml -- not login.phtml. This is actually the cause of your error. Finally, a better place to receive help on ZF related questions is on the mailing lists; fw-general for general questions, fw-mvc for MVC and forms related questions. You can find archives and links to signup instructions at http://framework.zend.com/archives. Yes, I think my key problems are re-writing though, I'm debugging with Apaches's RewriteLog and echo's in preDispatch(). I'll follow up in the forums though, Thx for the tips.
I don't normally bother, but I wanted to just take a minute and thank you immensely for your detailed tutorial here (wouldn't have hurt to have an authadapter example too, but I figured it out).
Zend Framework is quite large, quite powerful, and very adaptable...but it is a little overwhelming for someone to just "pick up". Some examples in the docs aren't "full" (at least they aren't functional by themselves) and all in all it's hard to put together a lot of the framework. Your tutorial was very clear and covered all the bases. I have my login/logout process working now, and I think it has given me exactly the "push" I needed to understand a lot of the framework. Thanks again! I know when you do things like this you get complaints and questions more often than praise, and I felt you deserved to be praised for your contribution in this case. Hello Matthew,
Thanks a lot for this nice post. As I am very new to Zend Framework, I've a lot of questions and confusions. And just now, my questions is, what will actualty the "getAuthAdapter(array $params)" function do and what will return? Can u please explain it a bit (and if you have time with an example)? Thanks again. Most authentication adapters accept parameters in their constructors that represent the credentials being verified. The number of parameters varies based on the adapter. getAuthAdapter() would simply take the credentials passed to it and pass them as the appropriate arguments to your authentication adapter constructor. You would then return the adapter instance.
Hi Mathew,
I am new to Zend framework and I am testing out Zend_Auth. Can you provide an example of what should be in the authAdapter. Specifically the authenticate() function as outlined in the the documentation. Even if it not code an explanation of whats required would be fine. Thanks in advance! The manual contains the information you need, and there are articles on Zend's DevZone as well as by others in the blogosphere. Google is your friend here.
is possible post an example for this :
public function getAuthAdapter(array $params) { // Leaving this to the developer... // Makes the assumption that the constructor takes an array of // parameters which it then uses as credentials to verify identity. // Our form, of course, will just pass the parameters 'username' // and 'password'. } Thanks for the tut, just what I needed.
I have created the login as specified, but keep having to re-login every 30 mins. How can I increase this time period? You'll need to setup Zend_Session configuration in your bootstrap -- look for the rememberMeSeconds method documentation of that class.
I also have this problem.
I login, but the session times out after a few minutes. It's really annoying. Has anyone come across this? Great tutorial But i have one question
When i am using this code i got this adapter error.... Catchable fatal error: Argument 1 passed to Zend_Auth::authenticate() must be an instance of Zend_Auth_Adapter_Interface, null given, Which authentication adapter would I need to use? and also, would the adapter go into the bootstrap file?
Use the authentication adapter that suits your particular application. If your credentials are in a database, you might want to use Zend_Auth_Adapter_DbTable. But the point is that Zend_Auth provides a variety of adapters -- *and* that it's trivial to create your own to use with it.
Just a simple question: where is the best point to check whether one is logged in? In bootstrap.php with the the adapter's methods, or just adding the first controller the login one in the router?
Grab your authentication session in your bootstrap, and push it into the relevant models.
Hello again. One more question, as it is not clear for me: how should LoginController get the execution control? With a router like this? Other better ideas?
$route = new Zend_Controller_Router_Route_Regex( '.*', array( 'controller' => 'login', 'action' => 'index' ) ); $router->addRoute('auth', $route); By default, you can leave off the '/index' action portion of the URL. When you process, create a "processAction", and have your form submit to that.
Better yet, create a "UserController" with a "loginAction" and "processLoginAction". i got some error during progmming,that is
Trying to get property of non-object in & code is $model2 = $this->_getModel2(); $artisian_artisianid =$model2->fetchEntry($data->baseuser_id); $artisian_active = $artisian_artisianid->artisian_account_active; $artisian_approve = $artisian_artisianid->artisan_account_approve; i got that error in last two line. please any one help me When i am using this code i got this adapter error....
Catchable fatal error: Argument 1 passed to Zend_Auth::authenticate() must be an instance of Zend_Auth_Adapter_Interface, null given, It's good, but it logs out automatically when I try to simultaneously load two pages of my site on different tabs of my browser. When doing so, Zend_Auth::getInstance()->hasIdentity() turns null and I get out of the account.
Add Comment
|
Calendar
QuicksearchLinks
CategoriesSyndicate This BlogShow tagged entries | |||||||||||||||||||||||||||||||||||||||||||||||||





Matthew Weier O?Phinney w swoim blogu zaprezentowa? bardzo ciekawy przyk?ad wykorzystania Zend_Auth i Zend_Form w celu autoryzacji i autentyfikacji u?ytkownik
Tracked: Mar 29, 07:53
Login and Authentication with Zend Framework - phly, boy, phly
Tracked: Mar 29, 10:15