WWW FAQs: How do I keep track of user sessions?


2006-11-14: Many websites benefit from the ability to keep track of a user's choices and preferences as they move through the site. Shopping carts are the most obvious example, but there are many reasons to keep "session" information about the user. So how do we do it?

The simplest way is to take advantage of the built-in session management features offered by PHP.

Every HTTP request from a web browser to a web server is essentially a completely separate conversation. Nothing is remembered from one request to the next. Programmers use many techniques to work around this, session cookies being the most effective and the least obnoxious to program. Fortunately, PHP is ready to take care of the tough part for you.

To remember a PHP variable from one page request to the next, all we have to do is call session_start() before we output absolutely anything at all to the web browser, to let PHP know we want sessions. Then we store our information in the $_SESSION associative array, like this. Note that you absolutely must call session_start() before you output any HTML at all. Don't output the doctype first. Don't output a blank line. Make <?php the very first thing in your PHP file. Otherwise the cookie won't be set properly. You must understand this or your sessions will not work.

This article emphasizes PHP. PHP is the most popular dynamic web extension language, and I don't have space to cover every possible solution. However, other dynamic web programming languages also offer session management solutions. For example, Perl programmers who use CGI.pm can store an associative array (Perl hash) in a session cookie and access it again later. Thoe who want a friendlier Perl-based solution can use CGI::Application. ASP programmers can use the ASP Session object. ASP.NET programmers can use a similar technique.


<?php
// At the VERY TOP of the page, before ANYTHING else.
// And I do mean ANYTHING. No blank lines, no anything.
// Yes, before the doctype (remember, the browser won't
// ever see this, it's server-side PHP code).
session_start();
?>

... HTML code follows ...

<?php
// On the first page request, the user picks their
// favorite color from a PHP form
session_start();
$_SESSION['color'] = $_POST['color'];
?>

// On a later page request, we retrieve the color
// the user has previously set
<?php
session_start();
echo "Your favorite color is " . $_SESSION['color'];
?>

In this simple example I am not validating the user's form input. Please be aware that users can submit anything, including values you did not expect, when filling out forms. Always make sure values submitted by the user are reasonable before trusting them.
"Great! That's really easy! But how does this work? Is it all in a gigantic HTTP cookie stored in the user's browser?"

Fortunately, no. PHP is smarter than that. PHP stores only a unique session identifier in a cookie, and retrieves the cookie later from a file kept on the server.

Though most programmers will never bump into them, there are some limitations on what can be stored in a session. For more information, see the official PHP session handling functions page.

Important note to administrators: by default, PHP stores session data in files in the folder /tmp. If this is not a reasonable location on your system— because your system runs Windows, or because the site is load-balanced and the /tmp folder is not shared between servers— then you will need to change this setting in php.ini. See this section of php.ini:


session.save_path = /tmp

If you are not the administrator of the web server and session storage does not work for you, contact the administrator and show them this page. If you are paying for PHP hosting, then working PHP sessions are a reasonable expectation on your part.

When The Browser Does Not Accept Cookies

As of November 2006, the vast majority of users do accept session cookies. They might not accept cookies that are intended to last longer than a single session, but for our purposes we don't need those.

However, some designers may be required to provide session management even for users who refuse cookies. This is possible, although obnoxious: every link on your site must contain a session ID. And that can mean extra coding on your part.

PHP To The Rescue: use_trans_sid

Adding the session ID to every link (and every form as well) sounds like a pain, doesn't it! Fortunately PHP offers a built-in alternative: the use_trans_sid option. When enabled, use_trans_sid will automatically insert the session ID into every link and every form on your page.
Of course, this has drawbacks— when users bookmark your page or, worse yet, link to it from another page, an old session ID will be part of the link. Fortunately a "stale" session ID in an old link doesn't break the link— the session variables simply won't be set. Still, this does mean that 100 users who don't accept cookies might link to your page with 100 different URLs. You might worry that search engines won't recognize that these are all the same animal and rank your page accordingly. Remember, though, that only the small fraction of users who won't accept cookies are seeing these special session ID URLs. The great majority of users will link to your site normally, so there shouldn't be any major consequences for your search engine rank.
use_trans_sid sounds great, doesn't it! Usually, though, it's not turned on automatically. This isn't such a bad thing - after all, many webmasters don't want their links rewritten by PHP. And of course there's a small performance price paid when PHP is forced to parse your HTML code, looking for links and forms.

So, how do we turn on use_trans_sid when we need it? Usually you can do this by creating a file called .htaccess (note the leading dot!) in your website's "document root" (home folder). In that file, place this single line:


php_flag session.use_trans_sid on

You can also do this in a sub-folder if you don't wish to enable this feature for your entire site.

If your .htaccess file already exists and contains other options, that's fine. Just add the above line at the end.

Those who have administrative access to their web servers can turn on this feature for all sites hosted on the server by editing php.ini instead:


session.use_trans_sid = 0

When changes are made to php.ini, the web server must be signaled to restart before those changes will go into effect.

use_trans_sid and Security

The use_trans_sid feature is a great workaround for users who won't accept cookies. But it does raise security concerns. Although sessions are only kept for three hours by default, there is still a risk that a naive user will innocently share a URL that contains a session data with another user, not realizing that this could allow the other person to hijack their "shopping cart." For this reason, it's important to verify the user's identity one last time at the end of the transaction. One way to do that is by asking for a credit card number (or other sensitive information) only at the very end of the process, and checking your database to make sure the transaction is not a duplicate.

If security is a very serious concern for you, I recommend that you not use use_trans_sid or the alternative workaround that follows, which has the same security issues. For some types of sites, such as online banking services, session cookies are simply mandatory.

One possible trick to slide past this problem: you can still rely on use_trans_sid if every link in the sensitive part of the site is a POST-method form submission. In this case, the session ID is always hidden in the form data and not shown in the URL. So the security risk of sharing the URL is eliminated. However, if you pursue this route, you must be very careful to completely avoid ordinary links and GET-method forms. It's probably a better idea to simply explain to users that they must enable cookies to use your site.

When You Can't Use use_trans_sid

If you are very unlucky, you might find that your website's version of PHP was not compiled with the --enable-trans-sid option. In English, this means that the administrator did not choose to include this option in their "build" of PHP, so you won't be able to use it.

By all means, ask them to reconsider that decision! But in the meantime, PHP does offer yet another workaround.

When the browser refuses cookies and the use_trans_sid feature is not enabled, PHP sets a special "constant" called SID. By manually adding SID to all of our links and form actions, we can still support sessions for users who refuse cookies, even without use_trans_sid. For regular links and POST-method forms, it's actually pretty easy, although you have to be very thorough and be sure to change every link and form action. And with a little elbow grease, we can also make it work for GET-method forms.

Changing Your Links To Embed the Session ID

Once you call session_start, PHP automatically sets a special constant called SID which contains the session ID information in a form that's ready to append to a URL's query string. So here's how to embed a session ID in an ordinary link:


<?php
session_start();
?>
<a href="/produce.php?<?php echo SID;?>">
Check Out Our Tomatoes
</a>

That's a bit tough to read, isn't it. Here's what's really going on.

First, we write an ordinary link like this:


<a href="/produce.php">Check Out Our Tomatoes</a>

Then, we add a ? after the URL, because the session ID is in the right format to appear as part of a query string that follows the ? in a URL.

Next, we output the session ID by "shifting gears" back into PHP mode with <?php and using an echo statement to output SID to the browser.

The official PHP sessions page recommends calling strip_tags on the value of SID before outputting it. With all due respect, I think they're wrong about this. There is no potential for XSS attacks here. PHP has already checked the session ID for dangerous characters at this point. Otherwise the session feature could be used to launch dangerous attacks on the file system regardless of whether you call strip_tags or not. However, you should follow their advice if you're nervous about this.

Finally, we shift back out of PHP mode with ?> and output the closing quote that ends the URL.

This does the job for simple links, and you can use the same technique for the src attribute of an image if you need it— that is, if you are generating the image on the fly and need to know the user's session information to do that correctly.

But what about forms? It's not obvious how to pass the session information as part of a form submission. The best fix is to use the POST method for the form, but continue to pass the session information in the query string of the form's action. In other words, do this:


<?php
session_start();
?>
<form method="POST" action="mypage.php?<?php echo SID;?>">
<!-- Form fields go here as usual -->
</form>

Even though the regular form fields are submitted via the POST method, PHP will still look for the session information in the query string. Problem solved!

But what if you must use a GET-method form? That's a bit thornier, because any fields already in the query string of the URL will be crushed by the browser when it prepares the form submission. But don't worry, I've sorted it out for you.

When it is present, the SID constant typically looks like this:


PHPSESSID=value

This is already set up to be submitted as part of a URL's query string. but in a GET-method form, we want to break apart the name (here, PHPSESSID) and the value and send them as the name and value of a hidden form field. And the following code takes care of that:


<?php
// Always at the very beginning of the file
session_start();
$pieces = explode("=", SID);
$sessname = "";
$sessid = "";
if (count($pieces) == 2) {
  $sessname = $pieces[0];
  $sessid = $pieces[1];
}
?>

... HTML code goes here ...

<form method="GET" action="mypage.php">
<?php
if ($sessname) {
?>
  <input type="hidden"
    name="<?php echo $sessname?>"
    value="<?php echo $sessid?>">
<?php
}
?>

... Your regular form fields go here ...

</form>

Here I use PHP's explode function to break the name and the value apart, producing an array with two elements— the name and value of the session ID. And later, I take advantage of the fact that PHP allows us to shift in and out of "PHP gear" any time we want... even right in the middle of an if statement. This allows us to avoid submitting an empty session ID field when a session cookie is already present. When cookies are present, the SID constant is empty, so my if test fails and the unneeded session ID name and value are not added to the form.

Conclusion

Sessions are simpler than you think. Though this article is long, most of it deals with special situations that most readers won't have to deal with. PHP makes session management easy; for the vast majority of sites, it's enough to call session_start at the top, and then fetch and retrieve whatever you like via the $_SESSION associative array. For those who must support users who are stubborn about accepting session cookies, use_trans_sid is a practical workaround. And even if your site is poorly administered, it is still possible to work around the lack of use_trans_sid using the SID constant.

Armed with the techniques in this article, you'll be able to readily recall your user's selections and carry the user's experience through to a successful conclusion. Your shopping cart runneth over.

Legal Note: yes, you may use sample HTML, Javascript, PHP and other code presented above in your own projects. You may not reproduce large portions of the text of the article without our express permission.

Got a LiveJournal account? Keep up with the latest articles in this FAQ by adding our syndicated feed to your friends list!