Scale your website by storing PHP Sessions in Database

I currently encountered an issue with PHP sessions when running multiple applications on different servers that needed to maintain the same session.

While the default session storage mechanism is adequate for many developers, you might find yourself wanting to modify its behavior from time to time. One of the most common reasons for wanting to change the default behavior is that the application needs to be able to run on multiple servers without server affinity (methods that direct requests from the same client to the same server). An easy way to make sure that sessions continue to work properly is to store sessions in a central database that is common to all servers.

Also the application needs to be able to run on a shared host, where there are significant security concerns associated with storing session data in the filesystem, therefore storing sessions in a database will make this safer.

PHP makes storing sessions in a database easy while still working with your common $_SESSION variable in PHP across your applications.

Here I have a simple class you can include in all your projects to store your sessions across your applications in a database.

First we need to create our session table. Just run this SQL script to create your session table, provided you already have a database you want to use.

CREATE TABLE IF NOT EXISTS `sessions` (
  `id` int(32) NOT NULL AUTO_INCREMENT,
  `http_user_agent` text NOT NULL,
  `hash` varchar(50) NOT NULL,
  `session_expire` int(50) NOT NULL,
  `session_data` text NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=0;

Next is the session class "session.class.php" that we will use.

class Session
{

    /**
     *  Constructor of class
     *
     *  @return void
     */
    function Session()
    {
    
        // set session lifetime
        $this->sessionLifetime = 3600; //this is 1hr
        // register the new handler
        
        session_set_save_handler(
            array(&$this, 'open'),
            array(&$this, 'close'),
            array(&$this, 'read'),
            array(&$this, 'write'),
            array(&$this, 'destroy'),
            array(&$this, 'gc')
        );
        register_shutdown_function('session_write_close');
        
        // start the session
        session_name('mysite'); // you can give your session id a name.
        session_start();
        
    }
    
    /**
     *  Regenerates the session id.
     *
     *  <b>Call this method whenever you do a privilege change!</b>
     *
     *  @return void
     */
    function regenerate_id()
    {

        // saves the old session's id
        $oldSessionID = session_id();
        
        // regenerates the id
        // this function will create a new session, with a new id and containing the data from the old session
        // but will not delete the old session
        session_regenerate_id();
        
        // because the session_regenerate_id() function does not delete the old session,
        // we have to delete it manually
        $this->destroy($oldSessionID);
        
    }

    /**
     *  Custom open() function
     *
     *  @access private
     */
    function open($save_path, $session_name)
    {
    
        return true;
        
    }
    
    /**
     *  Custom close() function
     *
     *  @access private
     */
    function close()
    {
        $this->gc($this->sessionLifetime); //do garbage collection
        return true;
    }
    
    /**
     *  Custom read() function
     *
     *  @access private
     */
    function read($session_id)
    {

        // reads session data associated with the session id
        // but only if the HTTP_USER_AGENT is the same as the one who had previously written to this session
        // and if session has not expired
        $result = @mysql_query("
            SELECT
                session_data
            FROM
                sessions
            WHERE
                hash = '".$session_id."' AND
                http_user_agent = '".$_SERVER["HTTP_USER_AGENT"]."' AND
                session_expire > '".time()."'
        ");
        
        // if anything was found
        if (is_resource($result) && mysql_num_rows($result) > 0) {

            // return found data
            $fields = mysql_fetch_assoc($result);
            // don't bother with the unserialization - PHP handles this automatically
            return $fields["session_data"];
            
        }
        
        // if there was an error return an epmty string - this HAS to be an empty string
        return "";
        
    }
    
    /**
     *  Custom write() function
     *
     *  @access private
     */
    function write($session_id, $session_data)
    {
    
        // first checks if there is a session with this id
        $result = @mysql_query("
            SELECT
                *
            FROM
                sessions
            WHERE
                hash = '".$session_id."'
        ");
        
        // if there is
        if (mysql_num_rows($result) > 0) {

            // update the existing session's data
            // and set new expiry time
            $result = @mysql_query("
                UPDATE
                    sessions
                SET
                    session_data = '".$session_data."',
                    session_expire = '".(time() + $this->sessionLifetime)."'
                WHERE
                    hash = '".$session_id."'
            ");
            
            // if anything happened
            if (mysql_affected_rows()) {
            
                // return true
                return true;
                
            }

        // if this session id is not in the database
        } else {

            // insert a new record
            $result = @mysql_query("
                INSERT INTO
                    sessions
                        (
                            hash,
                            http_user_agent,
                            session_data,
                            session_expire
                        )
                    VALUES
                        (
                            '".$session_id."',
                            '".$_SERVER["HTTP_USER_AGENT"]."',
                            '".$session_data."',
                            '".(time() + $this->sessionLifetime)."'
                        )
            ");
            
            // if anything happened
            if (mysql_affected_rows()) {
            
                // return an empty string
                return "";
                
            }
            
        }
        
        // if something went wrong, return false
        return false;
        
    }
    
    /**
     *  Custom destroy() function
     *
     *  @access private
     */
    function destroy($session_id)
    {

        // deletes the current session id from the database
        $result = @mysql_query("
            DELETE FROM
                sessions
            WHERE
                hash = '".$session_id."'
        ");
        
        // if anything happened
        if (mysql_affected_rows()) {
        
            // return true
            return true;
            
        }

        // if something went wrong, return false
        return false;
        
    }
    
    /**
     *  Custom gc() function (garbage collector)
     *
     *  @access private
     */
    function gc($maxlifetime)
    {
    
        // it deletes expired sessions from database
        $result = mysql_query("
            DELETE FROM
                sessions
            WHERE
                session_expire < '".(time() - $maxlifetime)."'
        ");
        
    }
    
}

Here is how we include this class in all our websites. This should most likely reside in a general class included in all the sites script.

include( "session.class.php" );
$session = new Session();

Conclusion

It is a relatively straightforward process to switch the recording of session data from files to a database. Here the session data is more secure as a potential hacker must be able to log into the database before he can access anything, the use of multiple servers would not create a problem as all session data now resides in a single central place and is accessible by all servers.

It is also easier to query the database if you require information about current sessions or current users.

Hope you now know why its a good practice to store your sessions in a database. Please comment below to give suggestions and if you have any issues implementing this code.

I write codes... web, mobile, desktop and hack stuffs

Leave a Reply

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