Session Management Using PHP, Part 1: Cookie-based Sessions

9
136

Most of us use sessions on a daily basis while browsing the Web. But have you ever wondered how sessions are implemented? The e-mail account we hold, the subscribed journal pages we read, the paid music channels we listen to—all these services use session management to identify their users. Session management provides two facilities: it protects the content from unauthorised access, and makes the same URL behave as per the requirements of the user.

Usually, most of us never care to think about what happens in the background when we enter our login credentials (user name and password). We probably consider this process quite trivial. In one of my recent projects to build a login-based set-up, I needed to create sessions to provide measured rights to the users. The problem that I considered trivial, began to bog me down. I went through a few difficulties while creating reliable sessions, and in the process learnt some lessons. Let me share them with you in this article.

The project was on the back burner for a while for the want of a reliable session management strategy. Most times, PHP session management silently failed and I could not make head or tail out of the problem (later on, I found that disabling cookies in the browser made the PHP sessions fail). You can say I looked like Charlie Chaplin—doing all the serious work and coming out with laughable results. An implementation of sessions using a server-side database worked correctly for me and I was able to successfully finish the project. The post-mortem of the ‘failure of PHP sessions’ showed that clients that block cookies cannot engage in a session.

What’s in a session?

A session means the duration spent by a Web user from the time logged in to the time logged out—during this time the user can view protected content. Protected content means the information that is not open to everyone (like your e-mail inbox). The beauty of a session is that it keeps the login credentials of users until they log out, even if they move from one Web page to another, in the same Web service, of course.

From the point of view of a server-side programmer, the server verifies the user name and password obtained from the client and permits the client to create a session if the data is valid. On successful verification of login credentials, a session ID is created for the user, which should either be stored on the server or the client. Once the login information is stored, all subsequent pages identify the user and provide the requested information.

The two parts of this article (the second part will be published next month) explain two alternative strategies for creating sessions and explain the pros and cons of both. On the client side (about which we’ll talk about in this issue), the session ID can be stored in a small file called a cookie. The cookie stores a name, a value, the server from which it originated, the time of creation, expiry time, etc. This file is stored on the client machine with the permission of the browser. The browser settings affect the storage of cookies on the client machine.

Although the cookie-based strategy is simple, it comes with a few weak links. People might peep into the client machine, find the cookie and misuse the name-value pair to cheat the server, disguising themselves as authentic users. Hence, cookie-based sessions are not recommended for financial transactions, as they don’t have the total assurance of privacy.

The second pitfall in cookie-based sessions is that a conservative user may block all incoming cookies. When the server sends a session cookie, it assumes that the client would store it. But, the client might reject the cookie for security reasons and thus hamper the formation of a session. This is what happened in my project. While some attempts to log in were successful, some were not. The reason being that blocking cookies was hampering sessions.

To make session management independent of cookies, a database was created on the server to store the session information. Each page of the site included a status-checking script, which queried the database, checked the validity of a session, and permitted further access only if the session was valid. This solution worked well, and I will present the procedure in the second part of this article.

Requirements for the project

I will use a LAMP stack to solve the session-management requirement. So, before proceeding into the project, you must verify the availability of the required services on the machine. It is assumed that a working Linux installation is available. Few words on Apache and MySQL servers might make things easy. Checking Apache and MySQL servers requires root user privileges. Either use the su - or sudo command to obtain root user privileges before running the /sbin/service tool.

Both Apache and MySQL run in the background as daemons, and their status can be checked using the /sbin/service command. The server can be in any one of the three states on the machine: a) the service is available and running; b) the service is available on the system, but not running—starting the service is easy; c) the service is not available on the machine—download and install the required service.

The availability of the Apache server on a machine may be tested by issuing the command /sbin/service httpd status on the command window. The response for this command might say ‘httpd is running’, wherein we can proceed to the next stage. If the service is available on the machine, but not currently running, the response would say ‘httpd is stopped’. No problem! Issue the command /sbin/service httpd start. The third possible response to /sbin/service httpd status command is ‘httpd: unrecognized service’, which means the Apache server needs to be installed on the machine.

Testing Apache and MySQL services
Figure 1: Testing Apache and MySQL services

Follow the same procedure to check the availability of the MySQL database server on the machine, after substituting mysqld for httpd in the previous command sequences. Figure 1 shows the command window and the browser window when the services are rightly installed on the system. Note that I’ve used a Fedora 9 installation for this article. If the service command is not the default command to start and stop services (daemons) on your system, please consult the documentation to find out the substituting command.

After ensuring that the services are available, check for the availability of the PHP scripting language by issuing the php -version command and look at the response. If the version number, build date, etc, are displayed, it means PHP is available. If a message stating “php: command not found” is displayed instead, then you need to install PHP, of course.

Once the Apache, MySQL and PHP servers are ready, you can readily run and test the scripts provided in this article. For those who use very old versions of the Apache server, PHP might require a few configurations; in recent versions, PHP does not require any further configurations.

Knowing how to work

For accessing content through a Web server, the data should be placed at the root of the Web server. In case of the Apache server, the documents for the server should be placed in the directory /var/www/html/ (or any other directory specified as DocumentRoot in your /etc/httpd/conf/httpd.conf file). Remember that all the HTML and PHP files mentioned in this article reside at the DocumentRoot, which is the /var/www/html/ directory in case of my Fedora 9 system. Placing any file in Apache’s DocumentRoot permits everyone to access the page, by accessing the server’s URL.

As for the database, at least one table is required for session management to store the user names and passwords to authorise login requests. Connect to your MySQL server by using the following command:

mysql -u <username> -p,

This will prompt for the MySQL password. After providing the password, once you get the mysql prompt, you can create a database called ‘session’ by issuing the following command:

create database session; use session;

Following this, you can create a table to store the user name and password by issuing the following command:

create table user (id bigint auto_increment, name varchar(50), pass blob, primary key(id), key(name));

This table is enough for the first part of the example, which handles sessions using a cookie.

For handling sessions using the database table on the server side (which we will deal with in Part 2 of this article), issue the following command at the mysql prompt:

create table session_log (session_id varchar(50), user_id bigint, remote_ip varchar(100), status enum('VALID','EXPIRED'), start datetime, last_access datetime, primary key(session_id),key(user_id));,

This will create the table required to store the session information. Figure 2 shows the commands as issued through the MySQL command line.

Creating the database tables for session management
Figure 2: Creating the database tables for session management

After creating the tables, insert at least one user into the user table. The typical command I used for the test case is as follows:

insert into user values(0,'admin',encode('good','session'));

This command inserts a user named ‘admin’ with the password ‘good’, encoded using the key session. Calling decode for the password requires the same key for a correct retrieval of the password.

Cookie-based sessions

PHP provides a cookie-based implementation for session management. The $_SESSION array is used for storing session data. PHP automatically generates a session ID and sends a session cookie containing this session ID to the client machine. The PHP functions for session management are listed in the following table.

Function Purpose
session_start() Initialises a session. If the session was not already started, it sends the session cookie to the client machine. If the session was already started, it loads the $_SESSION global variable with whatever values it was previously initialised with.
session_destroy() Destroys the session. The variable $_SESSION is cleared and the session cookie on the client is killed.

The basic login process begins with the display of two fields for the user name and password. The following code shows the HTML file used for the display of the login prompt (Figure 3 shows the login page):

<html><head><title>Login</title></head>
<body>
<form method="post" action="login.php">
<center>
<table border=0>
<tr>
<td><label for="username">Username:</label></td>
<td><input type="text" id="username" name="username" maxlength="50" /></td>
</tr> <tr>
<td><label for="passwd">Password:</label></td>
<td><input type="password" id="passwd" name="passwd" /></td>
</tr>
</table><br />
<input type="submit" value="Log in" />
</form>
</body></html>
The login page
Figure 3: The login page

Logging in

The user name and password are passed to the PHP script called login.php. The script uses the $_POST global variable to get the values filled against the user name and password fields. A connection is then established with the database ‘session’ and the corresponding user’s ID and password are retrieved. If the user name is found, the password stored in the database is matched against the password supplied by the user. If they do not match, the login attempt is rejected. Otherwise, the login succeeds. The following is what the login.php file looks like:

<?php
/*login.php*/
function check_login($username, $password)
{
if (!($username && $password))
return false;
$con=mysql_connect('127.0.0.1','user','pass') or die(mysql_error());
mysql_select_db('session',$con) or die(mysql_error());

$result = mysql_query("SELECT id, decode(pass,'session') as pass FROM user WHERE  name='" . addslashes($username) . "';",$conn) or die(mysql_error());

if(mysql_num_rows($result) != 1) {
printf("<center><h1>User %s not found</h1><br /><a href="login.html">Go to login page</a></center>", $username);
return false;
}
if(mysql_result($result, 0, 'pass') != $password) {
printf("<center><h1>Login attempt rejected for %s!</h1><br /><a href="login.html">Go to login page</a></center>", $username);
return false;
}
session_start();
$_SESSION['username'] = $username;
$_SESSION['id'] = mysql_result($result,0,'id');
mysql_close($conn);
return true;
}

if(check_login($_POST[‘username’], $_POST['passwd']))
printf("<center><h1>Welcome %s!</h1><br /><br />n",$_SESSION['username']);
print("<a href="status.php">Check status!</a><br /><a href="protectedimage.php">View Protected Image</a><br /><a href="logout.php">Logout</a></center>n");
?>

In case the user name and password are correct, the session_start() function is called, which, in turn, sends a session cookie containing the session ID of the user to the client machine. The cookie is shown in Figure 4. After this, calling $_SESSION['username'] or $_SESSION['id'] is permitted to store and retrieve session data. In the present case, the user name and user ID are stored in the $_SESSION array.

The session cookie (PHPSESSID) is displayed in Firefox's cookie list
Figure 4: The session cookie (PHPSESSID) is displayed in Firefox's cookie list

The session ID created by the session_start function is stored in a cookie on the client machine. You can inspect the cookie by accessing Edit–>Preferences from the Firefox menu, selecting the ‘Privacy’ tab, followed by clicking the ‘Show Cookies’ button. This displays the cookies sorted by the name of the server. In the present case, the server resides at 127.0.0.1 and the cookie is called ‘PHPSESSID’–you can notice this value displayed against ‘Content’ field on the information area. The welcome screen displayed on login is shown in Figure 5.
The welcome screen upon successful login
Figure 5: The welcome screen upon successful login

Session status

Since the session has been established, you can test the availability of persistence for the user name and user ID. For this, let’s create a small script called status.php. This script calls the session_start() function. Since the session cookie is already available in the client machine, calling the session_start() function looks at the session ID and loads the appropriate session variables with previous values on the server machine. Hence, calling $_SESSION['username'] or $_SESSION['id'] will retrieve the data stored through the login.php script file. The following is what the status.php script looks like:

<?php
/*status.php*/
session_start();
//Check for valid session. Exit from page if not valid.
if(!isset($_SESSION['username']) && !isset($_SESSION['id'])) {
print("<center><h1>invalid session!</h1><br />n<a href="loginform.html">Login</a>");
exit();
}
printf("<center><b>Welcome %s! Your id is: %d</b>",$_SESSION['username'],$_SESSION['id']);
printf("<br /><a href="logout.php">Logout %s</a></center>",$_SESSION['username']);
?>

The status script can be accessed by clicking on the ‘Check Status’ link on the login page. It displays the user name and user ID obtained from the session data. This fulfills the basic requirement of a session, since it permits persistence of data across different pages after login.

Creating protected pages

The very aim of a session is to create protected pages. A simple PHP script file is listed below that will protect an image from public access. The PHP script file protectediamge.php calls require_once('status.php') at the very beginning. This executes the status script once. The status script finds out the validity of the session, permits further movement if the session is valid and calls exit otherwise. The code for the protected image is shown below:

<?php
/*protectedimage.php*/
require_once('status.php');
print("<div align="center"><img src="summer.jpg" width=75%></div>");
?>

The protected image that is displayed after proper login is shown in Figure 6, while the same page (http://127.0.0.1/protectedimage.php) loaded without a valid session is shown in Figure 7. Look at the URL on the address bar of the browser in both the figures—the same URL displays the image when the session is available and denies access when the session is not available.

The image is protected content
Figure 6: The image is protected content

Access to protected content denied when session is not available
Figure 7: Access to protected content denied when session is not available

Log out

Now that we have checked how protected content works, it’s time to script the logout operation. The logout operation is contained in the script called logout.php. The script calls the session_destroy() function, which kills the session cookie and clears the session variables. The logout screen is shown in Figure 8. The following is what the logout script looks like:

<?php
session_start();
printf("<center><h1>Good Bye %s!</h1><br /><a href="loginform.html">Go to login page!</a><br /><a href="status.php">Get Status</a></center>n",$_SESSION['username']);
session_destroy();
?>

Logout screen
Figure 8: Logout screen

We might test whether the session was really terminated by calling the status.php file to check whether the name or the ID are still available. Figure 9 shows the message that the session is invalid. The name and ID are not available after destroying the session. Hence, including status.php at the beginning of each protected page ensures access is only possible after proper login, otherwise all other requests to the URL get terminated at the beginning itself.
Status message after destroying the session
Figure 9: Status message after destroying the session

Pros and cons of cookie-based sessions

Cookie-based session management provides the easiest way to manage sessions, especially since PHP provides built-in capabilities for this. However, there is also a strong reason why it should be avoided for professional websites because if the browser is set to block cookies, cookie-based session management fails. Another pitfall is that the cookie might fall into mischievous hands and result in loss of information. Hence, a cookie-based session is useful only for non-monetary and non-confidential websites.

The second part of this article [published in January 2009 issue] will explain server-side sessions using database tables.

9 COMMENTS

LEAVE A REPLY

Please enter your comment!
Please enter your name here