Zend authentication component (Zend_Auth), database storage class

Featured Image

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();
}
}
}

4 comments

  1. also I think if the timetoExpire which is optional parameter wasn’t set session was just expiring all the time

    I had to change
    if(time() – $result[self::$_requiredFieldAccessed] > self::$_timeToExpire)

    to
    if(self::$_timeToExpire && time() – $result[self::$_requiredFieldAccessed] > self::$_timeToExpire)

    cheers

    peter

  2. change
    $newStore = new AuthDbStorage($database, ‘MyCookie’, ‘user’, ’email’, 20);

    to
    $newStore = new Model_Auth_Storage_Db($database, ‘PHPSESSID’, ‘session’, ’email’);

    to get rid of notice.
    I would also change:
    if(Zend_Auth::getInstance()->hasIdentity())
    {
    Zend_Auth::getInstance()->getStorage()->write(
    Zend_Auth::getInstance()->getStorage()->read(),
    array(‘someKeyName’ => ‘Some val’, ‘someKeyName2’ => ‘Some val2’)
    );
    }

    to
    $auth->authenticate($adapter);
    but this way u lose that optional extraData to write parameter but I did not need it

    basically i’m using factory so I’ve changed more things

    my session table:
    CREATE TABLE `session` (
    `id` int(10) unsigned NOT NULL auto_increment,
    `sid` text,
    `uid` text,
    `hostname` varchar(255) default NULL,
    `accessed` int(11) default NULL,
    PRIMARY KEY (`id`)
    ) ENGINE=MyISAM AUTO_INCREMENT=26 DEFAULT CHARSET=latin1;

    sid and uid probably do not need to be text

    peter

  3. Thanks for the code snippets and explanation. I plugged this into our application in development and then realized that I had to create the session table, but didn’t have a schema for it! I’ve tried reverse engineering what the table should be with limited luck. Any chance you might want to share your table structure? 🙂

  4. Hi,

    Thanks for your code but I tried to use it and I have an error that says :

    “Notice: Undefined index: MyCookie in S:\wamp\www\cvwanted\application\classes\AuthDbStorage.php on line 43”

    So do I have to set the cookie first somewhere.

    Thanks for your advise.

    Gael

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.