Zend authentication component (Zend_Auth), database storage class

I love Zend Framework. Sometimes it can really be overhead but most of the time it’s really nice working in well organized, documented framework. One of the components I use relatively often iz Zend_Auth component. Zend Framework documentation is quite rich so I wont get into explaining how to use Zend_Auth. There is one thing that bothered me in Zend_Auth, and that’s persistent storage. Zend_Auth (in the latest version 1.7.6, as of time of this writing) supports only Session state as it’s storage. What I needed is full database session storage.
Great thing about Zend Framework components is that they come with Interface declaration. So all you need (for the most of the time) in order to replace some part (class) is to write your own class according to provided interface. Below is a working class for saving Zend_Auth state infromation in database.

In order for this to work, you need to call the setStorage method on Zend_Auth prior to calling authenticate method. In my example, I placed following code inside the bootstrap.php file

// Setup database connection
$params = array(
'host' => 'server', // localhost
'dbname' => 'user',
'username' => 'root',
'password' => 'somedbpass',
);
 
$database = Zend_Db::factory('Mysqli', $params);
 
// Set new session storage mechanizam
$newStore = new AuthDbStorage($database, 'MyCookie', 'user', 'email', 20);
Zend_Auth::getInstance()->setStorage($newStore);

Notice the definition of AuthDbStorage method. It needs 4 parameters to work (5th is optional). First parameter is database adapter, second is cookie name (you need cookie on client side, this is the only info on client side), third parameter is the name of the Zend_Auth table name (the one you set via setTableName), fourth parameter is Zend_Auth identity column name (the one you set via setIdentityColumn), and fifth optional parameter is time to expire a session (in seconds).

If you look at the write method in the class below, you will see it uses two parameters. By default, whatever you write to Zend_Auth default storage (the Session one) it gets overwritten. Here we have somewhat different situation. Here it set it up to accept optional parameter to write some info to additional database column named “extra”.

For writting values to current identity, use expression like

if(Zend_Auth::getInstance()->hasIdentity())
{
Zend_Auth::getInstance()->getStorage()->write(
Zend_Auth::getInstance()->getStorage()->read(),
array('someKeyName' => 'Some val', 'someKeyName2' => 'Some val2')
);
}

And here is the full class for Zend_Auth database storage.

< ?php class AuthDbStorage implements Zend_Auth_Storage_Interface { private static $_session = 'session'; private static $_requiredFieldUserId = 'uid'; private static $_requiredFieldSessionId = 'sid'; private static $_requiredFieldHostname = 'hostname'; private static $_requiredFieldAccessed = 'accessed'; // Extra field to store some info (use serialize) private static $_requiredFieldExtra = 'extra'; // Zend_Auth Adapter fields private static $_authAdapterIdentityColumn; private static $_authAdapterTableName; private static $_timeToExpire = 600; //in seconds private static $_cookieName; private $_db; public function __construct(Zend_Db_Adapter_Abstract $adapter, $cookieName, $authAdapterTableName, $authAdapterIdentityColumn, $timeToExpire = null) { $this->_db = $adapter;
self::$_cookieName = $cookieName;
self::$_authAdapterTableName = $authAdapterTableName;
self::$_authAdapterIdentityColumn = $authAdapterIdentityColumn;
self::$_timeToExpire = $timeToExpire;
}
 
/**
* Returns true if and only if storage is empty
*
* @throws Zend_Auth_Storage_Exception If it is impossible to determine whether storage is empty
* @return boolean
*/
public function isEmpty()
{
$result = $this->_db->fetchRow('SELECT * FROM '.self::$_session.' WHERE '.self::$_requiredFieldSessionId.' = ?', array($_COOKIE[self::$_cookieName]));
 
if(empty($result)) { return true; }
return false;
}
 
/**
* Returns the contents of storage
*
* Behavior is undefined when storage is empty.
*
* @throws Zend_Auth_Storage_Exception If reading contents from storage is impossible
* @return mixed
*/
public function read()
{
$result = $this->_db->fetchRow('SELECT * FROM '.self::$_session.' WHERE '.self::$_requiredFieldSessionId.' = ?', array($_COOKIE[self::$_cookieName]));
 
if(time() - $result[self::$_requiredFieldAccessed] > self::$_timeToExpire)
{
$this->clear();
return null;
}
//if (is_null($result)) { throw new Zend_Auth_Storage_Exception(); }
return $result;
}
 
/**
* Writes $contents to storage
*
* @param mixed $contents
* @throws Zend_Auth_Storage_Exception If writing $contents to storage is impossible
* @return void
*/
public function write($contents, $extraContents = array())
{
if(is_array($contents))
{
$userId = $contents[self::$_requiredFieldUserId];
}
 
else
{
$userId = $this->_db->fetchOne('SELECT * FROM '.self::$_authAdapterTableName.' WHERE '.self::$_authAdapterIdentityColumn.' = ?', array($contents));
}
 
$fields = array(
self::$_requiredFieldUserId => $userId,
self::$_requiredFieldSessionId => $_COOKIE[self::$_cookieName],
self::$_requiredFieldHostname => $_SERVER['REMOTE_ADDR'],
self::$_requiredFieldAccessed => time()
);
 
if(!empty($extraContents))
{
$fields[self::$_requiredFieldExtra] = serialize($extraContents);
}
 
// Before write, delete all session info
$this->clear();
 
// Check to see if session has expired
 
// Now write new one
$result = $this->_db->insert(self::$_session, $fields);
 
if($result == 0) { throw new Zend_Auth_Storage_Exception(); }
 
}
 
/**
* Clears contents from storage
*
* @throws Zend_Auth_Storage_Exception If clearing contents from storage is impossible
* @return void
*/
public function clear()
{
try
{
$this->_db->query('DELETE FROM '.self::$_session.' WHERE '.self::$_requiredFieldSessionId.' = ?', array($_COOKIE[self::$_cookieName]));
}
catch (Exception $ex)
{
throw new Zend_Auth_Storage_Exception();
}
}
}