Trick-Out Your Session Handler

April 10, 2006

Tutorials, Zend Framework

Introduction

Since HTTP uses a stateless request-response method to transfer web pages, all but the simplest web applications need a way to store data between requests. The best way to accomplish this in PHP is to use sessions. A session identifier is kept client-side in a cookie, and is matched up with data kept on the server, which is made available in scripts through the $_SESSION superglobal. By default, session data is stored on the filesystem in individual files. This works fine for most people, but when it comes to storing session data, PHP has several options available. This article will discuss some of the built-in options for session storage and also show you how to write your own session handler.

Different Strokes for Different Folks

There are a couple built-in options for storing session data. The session handler is set in the php.ini under the directive named

session.save_handler

files
By default, session data is saved in flat files. This option is usually fine for most projects. Performance degrades when the session directory contains thousands of session files because of the operating system’s ability to handle directories with numerous files. 4.0.1, you have the option to use a nested directory structure to store session files, but you’ll need to create that directory structure ahead of time, as well as implement your own garbage collection routine. Check the instructions in the php.ini file for more information on those options. The main concern with using files for session data is that they are normally kept in locations that are world readable, and on shared hosting this could be a security concern. Since PHP

sqlite
Optionally, you can store session data in an sqlite database. To do so, use a configuration such as:

session.save_handler = files
session.save_path = /tmp/phpsess.db

mm
For high-performance session storage, you can store session data in memory with the mm shared-memory module. You’ll need to compile php with the mm module support. Here is a tutorial to configure session handling with mm. Note that since session date is stored in RAM, you should consider it volatile data, and it’s lost with power outage or a reboot.

Note: the link in the above tutorial is outdated. You can retrieve the mm module from the OSSP.org website.

Other Note: I have not personally tested the mm module, so I can’t say whether or not it is current and works with PHP. If anyone is using this module, please leave a nice comment!

Do It Yourself

If you are using a shared hosting environment and are storing sensitive data in session, one way to secure it is to store the data in a database. To do this, you can write a custom session handler.

An additional problem arises if you have an application that is scaled across multiple web servers. It’s important that a user’s session data is available as he is bounced around different servers through the use of a load balancer or round-robin DNS. Saving the session data in a database that is accessible by all the servers takes care of the problem nicely.

Creating a custom session handler is not difficult, but there are few gotchas that are handy to know. Chris Shiflett has provided an example from his book Essential PHP Security, and I have an object-based sample for MySQL that you can download and edit to meet your needs. Both examples use a MySQL database and include the necessary table schema.

To tell PHP to use your custom session handler, use the function

session_set_save_handler()

You will need to write six callback functions for PHP to use, and you’ll specify them like:

session_set_save_handler("myopen", "myclose", "myread", "mywrite", "mydestroy", "mygc");

If you are using an object-based handler, you’ll specify the methods like so:

$sh = New SexySessionHandler();

session_set_save_handler(  
    array($sh,"open"),
    array($sh,"close"),
    array($sh,"read"),
    array($sh,"write"),
    array($sh,"destroy"),
    array($sh,"gc") );

Let’s take a look at these six functions, their parameters, and what values they should return.

open( $save_path, $session_name )
This function should open a connection to your database and return a boolean value indicating its success. In this case you don’t particularly need the save path and session name passed into the function, so you can ignore the values passed into the function.

close()
Your close function should close your database connection and return a boolean indicating its success.

read( $session_id )
Your read function is the only one that is a bit tricky. You should query your database for the record that matches the session identifier passed into the function, and return the session data as a string. Note that you do not need to unserialize the data as PHP takes care of that step. If no session is found for the session identifier, or if the session has no data, you MUST return an empty string–not NULL, false, or anything else. This is so important that I’ll be verbose:

return '';

right on!

write( $session_id, $session_data )
Your write function actually stores the session data in the database. The $session_id variable is your session identifier, the same value kept in the cookie, and by default is a 32 character string(this can be changed, so your database field should account for longer strings). The $session_data variable is the session data in serialized form. Your function should insert or update the appropriate record with the session data, and it’s a very good idea to store an access time so you can find stale sessions for garbage collection. Using the time() function is handy here because the expiration time from the garbage collection function is also made available as a Unix timestamp.

destroy( $session_id )
Your destroy function is a no-brainer. Delete the record in your database with the matching session identifier and return a boolean as the result of the operation.

gc( $max_expire_time )
The gc, or ‘garbage collection’ function is a housekeeping function to rid your database of old, nasty, stale session records. Nothing is worse than a session table with twenty thousand or so invalid session records. Your garbage collection function receives the maximum expire time set by your PHP configuration (from the session.gc_maxlifetime directive) in the form of a Unix timestamp. The function you provide should delete records older than the maximum expire time, and return a boolean value based on its success.

Using Your Custom Session Handler

As fun as it would be to talk about the complexities of using your shiny, tricked-out, custom session handler, I’m sorry to report there aren’t any! After you call session_set_save_handler() with your functions in working order, you can use sessions like your normally would. Remember that all the normal caveats apply:

  • You still call session_start() if you don’t specify session.autostart in php.ini.
  • You must load class definitions before you call session_start() if you want to save objects to the session
  • You can’t save something in a session if PHP can’t serialize it: resource variables like file handles and database links, as well as objects with circular references.
  • There’s an issue with object destructors and session handlers. The manual states: "Write and Close handlers are called after destructing objects since PHP 5.0.5. Thus destructors can use sessions but session handler can’t use objects. In prior versions, they were called in the opposite order. It is possible to call session_write_close() from the destructor to solve this chicken and egg problem."
  • Protect agains session fixation attacks.

Homework

If you want some practice writing a custom session handler, hop over to the Shared Memory Functions page and crank us out an example for some blazing fast in-memory session storage. Zend is still looking for a good general Session module for the Zend Framework, so get crackin’!

Conclusion

Wrapping up, I’ve shown you several ways to store session data depending on your application’s needs. You should consider the performance, security, and data lifespan of each of these methods to determine the appropriate storage mechanism. Remember, sessions are meant to be used for small amounts of data. Remember to validate and update session data appropriately, handle missing session data elegantly, and only store what you really need.

Digg This!

,

5 Responses to “Trick-Out Your Session Handler”

  1. dephiled Says:

    Small error in the "sqlite" section of the article:

    session.save_handler = files
    session.save_path = /tmp/phpsess.db

    Should be:

    session.save_handler = sqlite
    session.save_path = /tmp/phpsess.db

    Most people would have figured that out, though. The bummer here is this creates a SQLite2 database, not a SQLite3 database. Hopefully this will be changed in the future.

    A cool tip though, if you mount tmpfs on /tmp (or wherever you store your session database), your sessions will be stored in both a database and in RAM, and you don’t have to fuss with libmm. The best of both worlds, perhaps?

  2. lllgrklll Says:

    1. I installed the libmm
    2. compiled php with-mm

    however in Registered save handlers I don’t have mm just files user sqlite

  3. bigmweb1 Says:

    I’ve modified some code from another article here at Zend
    [ http://www.zend.com/zend/spotlight/code-gallery-wade8.php ]
    to accomplishing session locking using only the db (MyISAM table).

    what I posted there:

    MySQL MyISAM session handler with db-only session locking

    The code is quite different than posted here, so I’ve put it in its entirety on my website.

    I first noted with some debug statements in each function the order in which session functions are called. It became clear to me that the proper place to create sessions and check the lock status on existing sessions was in the _open function. We can grab the session id even if it is not passed in an argument.

    The other key to this working is we let mysql itself determine if a session can be locked by the particular page. If the insert or update fails, we were not able to get a lock and thus we must loop again. An insert will fail if we check for a lock, but the session already exists (another page/frame got there first). An update will fail if ses_locked does not equal "".

    http://mgrier.com/code/sess.php.html

    Mike

  4. jhherren Says:

    If you are interested in in-memory session handling, check out the MCache extension. Here is a recent article by Mohawk Software, the publishers of MCache. They have a good write-up on how to configure PHP for session handling here: http://www.mohawksoft.org/?q=node/8