Skip to Content

Zend Framework - Session Madness

Working with sessions in Zend Framework is both easy and hard at the same time. Easy because ZF provides Zend_Session for working with sessions. Hard because you have to understand the underlying PHP session framework AND the quirks and methods that ZF adds to sessions.

UPDATED: My first approach at this seemed to work, but ended up failing. The post has been revised with the approach we have proven to ourselves works properly.

ZF has session resource options. You set these options in the application.ini file. In theory once you do that you are done and your sessions will behave accordingly. In practice, I have yet to see this work right. Instead, I've had to mix the resource idea with the old school way of managing sessions.

For session management, there are security concerns that come into play - session hijacking, cross site scripting, etc. For the moment, I can ignore most of these issues because my application is only an internal app, and the users have physical access to the server. If they want to mess with the server, they don't need to use browser hacks. But that doesn't mean I shouldn't plan for these types of problems.

The first that comes up is where developers often assume that "if the session exists, then the user is logged in". The presence of a session is not enough. Did the person go away for a long period? Did the user log out, but the session not get cleared? So, one way to handle this is to make use of namespaces in your session, and build a namespace specifically for authorization or when the user was last active. In practice, this means simply adding another key to the $_SESSION array, where that key refers to another array of data. So you might see code similar to $_SESSION['auth']['username']. With ZF, we would refer to 'auth' as the namespace where the username is stored.

The big problem I've been having is that my session settings just would not get set. Using Zend_Session, the resources should have been picked up automatically, but that was failing and falling back to the default PHP session handling. So I had 24 minute sessions, with session files under /var/lib/php5 (on my Ubuntu server). After digging at this for a while I bit the bullet and moved back to the "old way" of doing things, but in a Zend manner.

I used the comments found at
Sam Huggill's Weblog
as a starting point. First you'll have to set your sesison resource values in the application.ini file:

resources.session.strict = off
resources.session.save_path = APPLICATION_PATH "/../data/sessions"
resources.session.use_only_cookies = true
resources.session.gc_maxlifetime = 18000 ;5 hour timeout
resources.session.remember_me_seconds = 3600 ;1hour after browser end

Next, I added this code to my bootstrap.php file:

protected function _initSession()
{
    // set up the session as per the config.
    $options = $this->getOptions();
    $sessionOptions = array(
        'save_path' => $options['resources']['session']['save_path'],
        'gc_probability' => $options['resources']['session']['gc_probability'],
        'gc_divisor' => $options['resources']['session']['gc_divisor'],
        'gc_maxlifetime' => $options['resources']['session']['gc_maxlifetime']
    );

    $idleTimeout = $options['resources']['session']['idle_timeout'];

    Zend_Session::setOptions($sessionOptions);
    Zend_Session::start();

    // now check for idle timeout.
    if(isset($_SESSION['timeout_idle'])) {
        //extract the data to local variables to make things easier to understand
        $stored = $_SESSION['timeout_idle'];
        $expires = $stored + $idleTimeout;

        if ($expires < time()) {
            Zend_Session::destroy();
            Zend_Session::regenerateId();
            header('Location: /login');
            exit();
        }
    }

    //We seem to be either a new session, or within the timeout of the current session,
    //so mark the time of this request, to be used on the next request to see if our session has expired.
    $_SESSION['timeout_idle'] = time();

    //now create our application specific namespace for the application variables
    //then store this namespace into the Zend Registry so it is available to our application pages.
    $session = new Zend_Session_Namespace("PINTS");
    Zend_Registry::set("session", $session);
}

So, whenever a page is loaded, we know the bootstrap.php file is loaded. This code then is executed as part of the framework setup, and before our application specific code is run. The first thing we do is to extract the information we need from the resources set in the application ini file into local variables. Then we set the Session options, and start the session.

Next we check if we have a timeout_idle value. This indicates how long until we consider the session to be expired. If we DO have a timeout_idle key on the $_SESSION array, we extract the value and calculate the time the session expires. Then we check if this expires time is less than current time, if so the session has expired, and we remove the session, generate a new session ID, redirect to the login page, and stop processing any other PHP code.

If the session didn't expire, we set the timeout_idle value to the current time, create the application specific namespace and stick it in the Zend Registry.

Now, getting the timeout routine to work just right for you may take some tweaking. It helps to understand that the time() function returns SECONDS since the epoch. Don't treat this value as milliseconds - things don't work when you do. (trust me. sighs.)

For testing, we added a line after the "$idleTimeout = options..." line that explicitly set idleTimeout to 10 seconds. This way we didn't have to wait around for hours to check the timeout.

Beyond that, you only really need an understanding of the various session options. Modify the $sessionOptions array to meet your specific needs. But the defaults I've shown above should be plenty.

Note: the remember_me_seconds does NOT set the timeout period for the session. This value indicates how long the session should be remembered AFTER the browser ends the session. For example you would use this if you want the user's session to last over a lunch break - even if the browser were closed. (assuming they didn't hit a log out link and explicitly close the session.) This is different from a standard timeout that says If the browser does not see activity for a certain period of time, close the session - even if the browser is still open to a page on your application.

To set the timeout, you need to set the gc_maxlifetime value. This indicates how many seconds before the PHP garbage collector will remove the session variables.

And with that, our sessions seem to be behaving properly now. We'll know tomorrow after our hours long timeout period passes.