I use a combination of a Zend_Controller_Plugin and an AuthController to protect my sites. It supports password resets, forced password changes, and automatic account locking, hence the moderate complexity.
Note that I use Doctrine, so this obviously can't just be cut and pasted into your application, but it should function with minimal changes. I removed a few methods specific to my application, but all the general authentication foo is there.
Plugin
<?php
class Hobo_Controller_Plugin_Auth extends Zend_Controller_Plugin_Abstract
{
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$auth = Zend_Auth::getInstance();
if ($auth->hasIdentity()) {
if ('logout' != $request->getActionName()) {
if (! $request->getParam('force_password_change')
&& $this->_checkPasswordExpiry($auth->getIdentity()->username)
) {
$request->setParam('force_password_change', true);
$request->setModuleName('default')
->setControllerName('auth')
->setActionName('change-password');
}
}
} else {
// Defer more complex authentication logic to AuthController
if ('auth' != $this->getRequest()->getControllerName()) {
$redirector = Zend_Controller_Action_HelperBroker::getStaticHelper('redirector');
$redirector->gotoSimple('restricted', 'auth');
}
}
}
protected function _checkPasswordExpiry($username)
{
// Look up user and return true if password is expired
}
}
AuthController
<?php
class AuthController extends Zend_Controller_Action
{
public function init()
{
$this->_auth = Zend_Auth::getInstance();
$this->_errorMessenger = new Zend_Controller_Action_Helper_FlashMessenger();
$this->_errorMessenger->setActionController($this)->init();
$this->_errorMessenger->setNamespace('error');
$this->_noticeMessenger = new Zend_Controller_Action_Helper_FlashMessenger();
$this->_noticeMessenger->setActionController($this)->init();
$this->_noticeMessenger->setNamespace('notice');
$this->view->errors = $this->_errorMessenger->getMessages();
$this->view->notices = $this->_noticeMessenger->getMessages();
}
public function preDispatch()
{
if (! $this->_auth->hasIdentity()) {
if (! in_array($this->_request->getActionName(), array(
'logout', 'identify', 'forgot-password', 'reset-password', 'restricted'))
) {
$this->_redirect('/auth/restricted');
}
}
}
public function restrictedAction()
{
// Shows access restricted page
}
public function logoutAction()
{
$this->_auth->clearIdentity();
Zend_Session::destroy();
$this->_redirect('/');
}
public function identifyAction()
{
if ($this->_request->isPost()) {
$username = $this->_getParam('username');
$password = $this->_getParam('password');
if (empty($username) || empty($password)) {
$this->_flashError('Username or password cannot be blank.');
} else {
$user = new dUser();
$result = $user->login($username, $password);
if ($result->isValid()) {
$user->fromArray((array) $this->_auth->getIdentity());
if ($this->_getParam('changepass') || $user->is_password_expired) {
$this->_redirect('auth/change-password');
return;
}
$this->_doRedirect($user);
return;
} else {
$this->_doFailure($result->getIdentity());
}
}
}
$this->_redirect('/');
}
public function changePasswordAction()
{
if ($this->_request->isPost()) {
$username = $this->_auth->getIdentity()->username;
$formData = $this->_request->getParams();
if (empty($formData['password'])
|| empty($formData['new_password'])
|| empty($formData['confirm_password'])
) {
$this->_flashError('Password cannot be blank.');
$this->_redirect('auth/change-password');
} elseif ($formData['new_password'] !== $formData['confirm_password']) {
$this->_flashError('Password and confirmation do not match.');
$this->_redirect('auth/change-password');
} else {
$user = new dUser();
$result = $user->login($username, $formData['password']);
if ($result->isValid()) {
$user->updatePassword($username, $formData['new_password']);
$this->_flashNotice('Password updated successfully!');
$this->_redirect('/');
} else {
$this->_flashError('Invalid username or password!');
$this->_redirect('auth/change-password');
}
}
}
if ($this->_getParam('force_password_change')) {
$this->view->notice = 'Your password has expired. You must change your password to continue.';
}
}
public function forgotPasswordAction()
{
if ($this->_request->isPost()) {
// Pseudo-random uppercase 6 digit hex value
$resetCode = strtoupper(substr(sha1(uniqid(rand(),true)),0,6));
Doctrine_Query::create()
->update('dUser u')
->set('u.reset_code', '?', array($resetCode))
->where('u.username = ?', array($this->_getParam('username')))
->execute();
$this->_doMail($this->_getParam('username'), $resetCode);
$this->_flashNotice("Password reset request received.");
$this->_flashNotice("An email with further instructions, including your <em>Reset Code</em>, has been sent to {$this->_getParam('username')}.");
$this->_redirect("auth/reset-password/username/{$this->_getParam('username')}");
}
}
public function resetPasswordAction()
{
$this->view->username = $this->_getParam('username');
$this->view->reset_code = $this->_getParam('reset_code');
if ($this->_request->isPost()) {
$formData = $this->_request->getParams();
if (empty($formData['username']) || empty($formData['reset_code'])) {
$this->_flashError('Username or reset code cannot be blank.');
$this->_redirect('auth/reset-password');
} elseif ($formData['new_password'] !== $formData['confirm_password']) {
$this->_flashError('Password and confirmation do not match.');
$this->_redirect('auth/reset-password');
} else {
$user = new dUser();
$result = $user->loginWithResetCode($formData['username'], $formData['reset_code']);
if ($result->isValid()) {
$user->updatePassword($result->getIdentity(), $formData['new_password']);
$user->fromArray((array) $this->_auth->getIdentity());
$this->_flashNotice('Password updated successfully!');
$this->_doRedirect($user);
} else {
$this->_doFailure($result->getIdentity());
$this->_redirect('auth/reset-password');
}
}
}
}
protected function _doRedirect($user)
{
$this->_helper->Redirector->gotoUserDefault($user);
}
protected function _flashError($message)
{
$this->_errorMessenger->addMessage($message);
}
protected function _flashNotice($message)
{
$this->_noticeMessenger->addMessage($message);
}
protected function _doFailure($username)
{
$user = Doctrine_Query::create()
->from('dUser u')
->select('u.is_locked')
->where('u.username = ?', array($username))
->fetchOne();
if ($user->is_locked) {
$this->_flashError('This account has been locked.');
} else {
$this->_flashError('Invalid username or password');
}
}
}