Session Management with PHP
In the past few issues we've been looking at the good, the bad, and the ugly of our long and illustrious history with the Telnet protocol. To briefly recap, the biggest benefit that Telnet offers is a persistent connection to our MultiValue applications. Unfortunately, we pay for this benefit by suffering through dropped connections, insecure communications, and antique-looking façades on otherwise excellent applications.
In contrast to Telnet, web applications using HTTPS can offer us excellent security, state-of-the-art user experiences, and most importantly HTTPS can offer improved reliability by reducing our dependence on persistent connections. While these are all Beautiful Things, there is one significant difference that cannot be overlooked — web-based communications are stateless; each request to the server is a brand-new connection to the application.
Without state, an application has no way of knowing it has seen you before. For example, when connecting to our MultiValue applications, we typically start by logging in to the server. Imagine what might happen if the server forgot you immediately after logging in! Yet this is exactly what happens with web-based communications; the moment each transaction is complete, the server forgets you were ever there.
The PHP language, however, offers a stellar example of how state can be managed in a web-based application. While there are a multitude of web technologies and languages from which to choose we'll look at PHP today because the PHP solution is not only effective, it's curiously simple.
In PHP, state management is handled through sessions. A script that needs to manage state simply starts a session and from that moment on a single variable named $_SESSION can be used to store information that can be recalled later.
"That's it?" I can hear you say. "Session management is handled using ONE variable? How effective can ONE variable be?" Considering that one variable is an associative array and associative arrays can hold any amount or structure of information (memory limits notwithstanding) that one variable could hold every bit of information in your application and still have room to grow.
To visualize this in MultiValue terms, think of this one variable as a big block of named common memory. This block can then hold other variables that can themselves hold more variables to a virtually unlimited depth. Associative arrays are a very powerful feature of the PHP language, but like all regular variables these arrays disappear when the script terminates. With session management, however, this $_SESSION variable sticks around until you say you're done with it or the user closes the browser, whichever comes first. PHP does this by saving this variable to disk when the script terminates and reloading that variable from disk the next time the session is needed.
Figure 1 illustrates a simple example of how this might be used. On line 3, the script calls a function session_start(). Believe it or not, this is all you have to do to enable session management in PHP. Once the session has been started we can work with the $_SESSION variable like any other variable, with one notable exception: Anything we leave in this variable at the end of our script will be available the next time we run this — or any — script on this site that uses session_start().
01<?php 02// Start a new session; this will create the 03// $_SESSION variable 04session_start(); 05// Initialize our persistent variable 06if(isset($_SESSION['number']) == false) 07{ 08$_SESSION['number'] = 0; 09} 10// ...increment it... 11$_SESSION['number'] += 1; 12// ...and show the visitor what we've done. 13echo $_SESSION['number']; 15?>
Fig. 1
On line 6 we use the isset() directive to determine if a particular variable exists in the $_SESSION array. This will tell us if this is our first time with this session or if we're coming back to a session that has been previously established. If our $_SESSION array does not have an entry called "number", we can say with confidence that this is our first time with this variable and initialize it to zero. Once all this housekeeping is done we can increment our variable and echo (print) the result. (See intl.spectrum.com/s1043 to see this script in action.)
The first time this script runs it will print 1. Refresh the browser and it will print 2, then refresh again for 3, and the cycle continues. When the browser is restarted this sequence will start again at 1 because PHP only holds on to the session identifier as long as the browser is running. Close and reopen the browser and PHP will generate a new session and start over from 1.
There are times, however, when we may want our script to hold values indefinitely, even between browser restarts. Figure 2 shows a small tweak that enables this functionality. The session_id() function allows us to name our session so that anyone using this ID can access our session variables. If you're security conscious, this should raise a few hairs on the back of your neck as this will tell the script to not only retain your values across browser restarts, it will allow everyone to share — and potentially update — the same values! (To see this script in action, simply point your browser to intl-spectrum.com/s1044.)
01<?php 02// Give the session an ID 03session_id('spectrum'); 04 05// Start the session using the ID provided 06session_start(); 07if(isset($_SESSION['number']) == false) 08{ 09$_SESSION['number'] = 0; 10} 11 12$_SESSION['number'] += 1; 13echo $_SESSION['number']; 14?>
Fig. 2
Here's the problem: Sessions in PHP are really disk files containing a serialized cache of variables. Using the session_id() function, we are effectively setting the name of that file. If we set this to a fixed name, as this example illustrates, every user will share access to that one file and its contents. This could be useful in some cases or a Very Bad Idea in others.
What about these sites where the site remembers my information between browser reboots but doesn't get my information confused with yours? Figure 3 illustrates how this might work. When we're initializing our session variable (line 15) we can also set up a cookie on the user's browser (lines 17-18) containing their current session ID. Per the example, the browser will expire this cookie 24 hours into the future, but until then this gives us a place on their workstation where we can cache their session ID. (See intl-spectrum.com/s1045 to see this in action.)
01<?php 02// Look for cookie value from the user's browser. 03// If it's set, we'll assume 04// that the cookie holds the session ID to use. 05if(isset($_COOKIE['spectrum'])) 06{ 07session_id($_COOKIE['spectrum']); 08} 09 10session_start(); 11if(isset($_SESSION['number']) == false) 12{ 13$_SESSION['number'] = 0; 14 15// Initialize the cookie 16// expire in 86400 seconds (1 day) 17$expire = time() + 86400; 18 19setcookie('spectrum',session_id(),$expire); 20} 21 22$_SESSION['number'] += 1; 23echo $_SESSION['number']; 24?>
Fig. 3
It's important to recognize that anytime we use user input or information stored on the user's browser we run the risk of a security breach. A seasoned hacker could simply edit their cookie to change their PHP session ID and our script would never know that the change had occurred. Fortunately, we have a number of options for mitigating this risk.
In Figure 4 we've added a little more security by storing the IP address of the user in their session variable on the web server. The next time PHP loads that session, if the IP address of the session doesn't match the user's current IP address, we can destroy their current session, unset (erase) the current session information, and start a brand new session for the current user. The upside to this is that for a hacker to breach your site they would have to come to your site on the same IP address as the original user, which is unlikely. The down side to this, however, is if your site visitor routinely accesses your site from a computer with a changing IP address (as can be the case with different VPN connections) this script will see them as a hacker and will reset their session with some frequency. Still, that's a small price to pay in this world of spammers and scammers where security is and must remain a primary concern. Of course, there are numerous more complex techniques available for greater security of session information.
01<?php 02if(isset($_COOKIE['spectrum'])) 03{ 04session_id($_COOKIE['spectrum']); 05} 06 07session_start(); 08 09// Check to see if this session has an embedded IP 10// address. If it does, we 11// need to compare this to the current visitor's IP 12// address and reset the 13// session if they aren't the same. 14if(isset($_SESSION['ipAddr'])) 15{ 16if($_SESSION['ipAddr'] != $_SERVER['REMOTE_ADDR']) 17{ 18// delete the session file on the server 19session_destroy(); 20// Unload session variables in memory 21unset($_SESSION); 22session_start();// Start a new session 23} 24} 25 26// Initialize our persistent variable and cookie 27if(isset($_SESSION['number']) == false) 28{ 29$_SESSION['number'] = 0; 30$_SESSION['ipAddr'] = $_SERVER['REMOTE_ADDR']; 31 32// expire in 86400 seconds (1 day) 33$expire = time() + 86400; 34setcookie('spectrum',session_id(),$expire); 35} 36 37$_SESSION['number'] += 1; 38echo $_SESSION['number']; 39?>
Fig. 4
Security aside, you may wonder about the performance of this kind of state management where the information is stored on disk and retrieved each time. Remarkably, it can be excellent. You certainly won't want to store gigs of information in the $_SESSION variable to be saved and restored on each connection, but looking at a typical MultiValue application you wouldn't typically hold gigs of information in memory anyway. (Sure, there are exceptions, but that doesn't make it a good idea.)
Overall, session management in PHP makes managing state very easy. With the session information read from and written to disk on each connection, a network hiccup is much less likely to create a mess of disconnected sessions. By accessing the application via HTTPS, we can get the best security available today. Finally, these technologies can open our applications to the widest audience possible while allowing us to leverage some of the most sophisticated user interfaces available.
Managing state is certainly no concern. Furthermore, all this is available right now. So what are we waiting for?