Приглашаем посетить
Чулков (chulkov.lit-info.ru)

Client-Side Sessions

Previous
Table of Contents
Next

Client-Side Sessions

When you visit the doctor, the doctor needs to have access to your medical history to effectively treat you. One way to accomplish this is to carry your medical history with you and present it to your doctor at the beginning of your appointment. This method guarantees that the doctor always has your most current medical records because there is a single copy and you possess it. Although this is no longer common practice in the United States, recent advances in storage technology have advocated giving each person a smart card with his or her complete medical history on it. These are akin to our client-side sessions because the user carries with him or her all the information needed to know about the person. It eliminates the need for a centralized data store.

The alternative is to leave medical data managed at the doctor's office or HMO (as is common in the United States now). This is akin to server-side sessions, in which a user carries only an identification card, and his or her records are looked up based on the user's Social Security number or another identifier.

This analogy highlights some of the vulnerabilities of client-side sessions:

  • There is a potential for unauthorized inspection/tampering.

  • Client-side sessions are difficult to transport.

  • There is a potential for loss.

Client-side sessions get a bad rap. Developers often tend to overengineer solutions, utilizing application servers and database-intensive session management techniques because they seem "more enterprise." There is also a trend among large-scale software design aficionados to advance server-side managed session caches ahead of heavyweight sessions. The reasoning usually follows the line that a server-based cache retains more of the state information in a place that is accessible to the application and is more easily extensible to include additional session information.

Implementing Sessions via Cookies

In Chapter 13, cookies were an ideal solution for passing session authentication information. Cookies also provide an excellent means for passing larger amounts of session data as well.

The standard example used to demonstrate sessions is to count the number of times a user has accessed a given page:

<?php
        $MY_SESSION = unserialize(stripslashes($_COOKIE['session_cookie']));
        $MY_SESSION['count']++;
        setcookie("session_cookie", serialize($MY_SESSION), time() + 3600);
?>
You have visited this page <?= $MY_SESSION['count'] ?> times.

This example uses a cookie name session_cookie to store the entire state of the $MY_SESSION array, which here is the visit count stored via the key count. setcookie() automatically encodes its arguments with urlencode(), so the cookie you get from this page looks like this:

Set-Cookie: session_cookie=a%3A1%3A%7Bs%3A5%3A%22count%22%3Bi%3A1%3B%7D;
expires=Mon, 03-Mar-2003 07:07:19 GMT

If you decode the data portion of the cookie, you get this:

a:1:{s:5:"count";i:1;}

This is (exactly as you would expect), the serialization of this:

$MY_SESSION = array('count' => 1);

Escaped Data in Cookies

By default PHP runs the equivalent of addslashes() on all data received via the COOKIE, POST, or GET variables. This is a security measure to help clean user-submitted data. Because almost all serialized variables have quotes in them, you need to run stripslashes() on $_COOKIE['session_data'] before you deserialize it. If you are comfortable with manually cleaning all your user input and know what you are doing, you can remove this quoting of input data by setting magic_quotes_gpc = Off in your php.ini file.


It would be trivial for a user to alter his or her own cookie to change any of these values. In this example, that would serve no purpose; but in most applications you do not want a user to be able to alter his or her own state. Thus, you should always encrypt session data when you use client-side sessions. The encryption functions from Chapter 13 will work fine for this purpose:

<?php
// Encryption.inc
  class Encryption {
   static $cypher     = 'blowfish';
   static $mode       = 'cfb';
   static $key = 'choose a better key';

   public function encrypt($plaintext) {
     $td = mcrypt_module_open (self::$cypher, '', self::$mode, '');
     $iv = mcrypt_create_iv (mcrypt_enc_get_iv_size ($td), MCRYPT_RAND);
     mcrypt_generic_init ($td, self::$key, $iv);
     $crypttext = mcrypt_generic ($td, $plaintext);
     mcrypt_generic_deinit ($td);
     return $iv.$crypttext;
   }
   public function decrypt($crypttext) {
     $td = mcrypt_module_open (self::$cypher, '', self::$mode, '');
     $ivsize = mcrypt_enc_get_iv_size($td);
     $iv = substr($crypttext, 0, $ivsize);
     $crypttext = substr($crypttext, $ivsize);
     $plaintext = "";
     if ( $iv ) {
     mcrypt_generic_init ($td, self::$key, $iv);
       $plaintext = mdecrypt_generic ($td, $crypttext);
       mcrypt_generic_deinit ($td);
     }
     return $plaintext;
    }
  }
?>

The page needs a simple rewrite to encrypt the serialized data before it is sent via cookie:

<?php
  include_once 'Encryption.inc';
  $MY_SESSION = unserialize(
                  stripslashes(
                    Encryption::decrypt($_COOKIE['session_cookie'])
                  )
                );
  $MY_SESSION['count']++;
  setcookie("session_cookie", Encryption::encrypt(serialize($MY_SESSION)),                
Client-Side Sessions                                                       time() + 3600);
?>

From this example we can make some early observations about heavyweight sessions. The following are the upsides of client-side sessions:

  • Low back-end overhead As a general policy, I try to never use a database when I don't have to. Database systems are hard to distribute and expensive to scale, and they are frequently the resource bottleneck in a system. Session data tends to be short-term transient data, so the benefits of storing it in a long-term storage medium such as an RDBMS is questionable.

  • Easy to apply to distributed systems Because all session data is carried with the request itself, this technique extends seamlessly to work on clusters of multiple machines.

  • Easy to scale to a large number of clients Client-side session state management is great from a standpoint of client scalability. Although you will still need to add additional processing power to accommodate any traffic growth, you can add clients without any additional overhead at all. The burden of managing the volume of session data is placed entirely on the shoulders of the clients and distributed in a perfectly even manner so that the actual client burden is minimal.

Client-side sessions also incur the following downsides:

  • Impractical to transfer large amounts of data Although almost all browsers support cookies, each has its own internal limit for the maximum size of a cookie. In practice, 4KB seems to be the lowest common denominator for browser cookie size support. Even so, a 4KB cookie is very large. Remember, this cookie is passed up from the client on every request that matches the cookie's domain and path. This can cause noticeably slow transfer on low-speed or high-latency connections, not to mention the bandwidth costs of adding 4KB to every data transfer. I set a soft 1KB limit on cookie sizes for applications I develop. This allows for significant data storage while remaining manageable.

  • Difficult to reuse session data out of the session context Because the data is stored only on the client side, you cannot access the user's current session data when the user is not making a request.

  • All session data must be fixed before generating output Because cookies must be sent to the client before any content is sent, you need to finish your session manipulations and call setcookie() before you send any data. Of course, if you are using output buffering, you can completely invalidate this point and set cookies at any time you want.

Building a Slightly Better Mousetrap

To render client-side sessions truly useful, you need to create an access library around them. Here's an example:

// cs_sessions.inc
require_once 'Encryption.inc';
function cs_session_read($name='MY_SESSION') {
  global $MY_SESSION;
  $MY_SESSION = unserialize(Encryption::decrypt(stripslashes($_COOKIE[$name])));
}
function cs_session_write($name='MY_SESSION', $expiration=3600) {
  global $MY_SESSION;
  setcookie($name, Encryption::encrypt(serialize($MY_SESSION)),
                                       time() + $expiration);
}
function cs_session_destroy($name) {
  global $MY_SESSION;
  setcookie($name, "", 0);
}

Then the original page-view counting example looks like this:

<?php
  include_once 'cs_sessions.inc';
  cs_session_read();
  $MY_SESSION['count']++;
  cs_session_write();
?>
You have visited this page <?= $MY_SESSION['count'] ?> times.


Previous
Table of Contents
Next