PHP sessions under the microscope: Part 1

I will start by saying a few things for the purpose of sessions. As we know, the main web protocol, which most communications are based on, is HTTP. By nature, HTTP is a stateless protocol. Requests are not associated to each other and, because of this, they should contain enough information on their own to serve the request. So, the protocol itself does not require the server to retain information about the user who made the request and whether or not this user have made other requests in the recent past.

Of course, there are many times when the stateless nature of HTTP does not serve our web application’s functionality well enough. Consider the case of a web application serving protected/private content. Authentication would be needed for every request made by users. But having the user supply its username and password in each and every request would be an overkill. We need the user to login once and then be able to access the protected content as many times as he/she wants within a reasonable time frame. In order to do that, we must be able to tell that an incoming request originates from the same user who was authenticated a few requests ago. So, we need to keep some state in our web application, which will help us correlate requests coming from the same user. Sessions are a way to implement this time-correlation between a user’s requests.

In order to understand what is really a session and how can we utilize a session, in PHP, to achieve our purposes, we need to have a look at how it works. There are 6 basic, server-side, actions related to sessions:

⦁ create a session
⦁ start a session
⦁ updating the session data
⦁ regenerating a session
⦁ end a session
⦁ delete a session

Around these 6 actions we will setup the whole session functionality and talk about some more things related to sessions that have to do with the browser. Let’s take a first idea by examining the first 2 actions: creating and starting a session.

How a session is created and started: When we call the session_start() function , the server checks if there is already a session for this user. It does this by checking whether the browser has sent a session cookie. This is a cookie with a special name. By default, a session cookie should be named “PHPSESSID”, but this name is configurable.

If no session cookie has been sent, a new session is created: The server picks a random string as a session identifier (or “session ID”) and it creates a file named “sess_<session_id>”, where <session_id> is the random identifier that was just created. We will call the place where the session file is saved, the “session store”. After that, it tells the browser (by setting appropriately the response headers) to create a cookie with name “PHPSESSID” and content the session identifier.

* just as a reminder, when a browser makes an HTTP request to a URL that belongs to a specific domain , it piggybacks (in the request headers) all cookies that are related to this domain.

If a session cookie has been sent, the existing session is loaded: The server reads the session ID from the cookie and looks in the session store for a file named “sess_<session_id>”. It reads from the file all the variables that have been stored and puts them in the global $_SESSION variable. At the same time, it saves the session file (again) in order to set the current date as the “last modified” date of the file (which is also the most recent time the session was used).

As you can see, we can already identify three main components to the session functionality. The first is the session store, which is used to persist the information about a session. The second is the global variable $_SESSION, which acts as temporary storage of the session data. PHP code does not act directly on the session file but it reads and writes session-related information through this variable. And the third component is the session cookie. This is the way for a user to inform the server that a series of requests is coming from him. Putting aside the case where a session ID has been stolen, all requests that carry a session cookie with the same session ID are coming from the same browser and so from the same user.

Updating (saving or deleting) session data: As we said, when a session is started, a global variable named $_SESSION will be available throughout the request. This variable does not exist before the call of the session_start() function. The session data are loaded to this variable when session_start is called and we can modify these data:

$_SESSION[‘username’] = ‘Alexandros’;

unset($_SESSION[‘role’]);

Updating the $_SESSION variable will not automatically change the session’s data that are in the session store. Granted that a session has been started, the contents of the $_SESSION variable will be written to the session store at the end of the script execution. If we want this to happen earlier, we can execute the session_write_close() function or the session_commit() function, which is just an alias of the first. The $_SESSION variable is still available after the session ends but any changes that happen to the $_SESSION after the session has been ended, will not be saved into the session store.

The function session_unset() clears the $_SESSION array. Practically, it is equivalent to:

$_SESSION = [];

The session data are still intact in the session store and the session is not destroyed. The same session ID is still active for this user. Of course, after the script execution, the empty $_SESSION’s contents will be saved in the session store, removing any session data (not the session file) from there.

A note on session handlers: We said, so far, that session data are persisted into a file (the session file) but this is not always true. It is just the default behavior and it really depends on the value of the session.save_handler configuration parameter. It defaults to “file”. This is a handler inherently supported by PHP. Alternatives, that may be offered by some PHP modules, include storing the session data to a database, to memcached, etc.

How a session ends: Ending a session means that we are not able anymore (during the script execution) to modify the session data in the session store. A session can be ended in three ways:

a) Automatically, when the script execution ends. In this case, the data contained in the $_SESSION will be saved in the session store,replacing whatever was contained there.

b) By calling either the session_write_close() or the session_commit() function. Again, whatever is in the $_SESSION variable is saved into the session store.

b) By calling the session_destroy() function. This function deletes any session data from the session store. However, it does not affect the $_SESSION variable neither it deletes the session file. The session continues to exist. It does, though, ends the session. And as expected, after calling it, any changes to $_SESSION variable will not be persisted.

Let’s point out here that ending a session does not prevent us from starting again the session before the script execution finishes.

Some words about cookies and the session cookie

The lifetime of a cookie: All cookies has a lifetime and this is defined in seconds. By default, this lifetime is 0, which means that the cookie is valid till the browser window that created the cookie is closed (the whole window, not just a tab). When the browser windows closes, the cookie is deleted. Of course, we can use a positive number for the cookie lifetime. This can be done with the session_set_cookie_params() function, which changes the default cookie lifetime value. This change will be valid only for the lifetime of the specific script execution and, in order to take effect, this change should be made before the creation of the cookie, so before the relevant response headers are set and sent by session_start.

session_set_cookie_params(30);

In this case, the cookie will not be deleted after the browser window closes, no mater whether the cookie is expired or not. However, the expired cookies are never sent by the browser. Expired cookies will be replaced by a new one after the next execution of session_start() .

The expiration of the session cookie: A session is not expired when the session cookie is expired. The session is a server’s responsibility and only the server can delete a session. Moreover, we cannot leave the session expiration or destruction to the browser’s discretion. The browser cookies can be tampered and, moreover, a request may not even come from a browser. After the session cookie expires, the session file remains on the server but (under normal circumstances) is not used since the browser is not sending anymore the relevant cookie. In order to prevent flooding the server with files, a periodic clean up takes place. We will deal with this clean up later in this article.

A session cookie can also be forced to expire by moving its expiration time to the past:

setcookie(session_name(), “”, time() – 3600);

How a session is deleted: As we have seen, the session cookie expiration deactivates the usage of the session at the client’s side. The user’s impression is that the session has ended but the session is still active on the server side and it still contains the session data. Even calling the session_destroy() function is not enough to make the session go away. It will just destroy the session data. If, for some reason, trying to force the session cookie expiration through setcookie() fails to execute, the browser will continue to use the same session. So, the server runs a session clean up periodically and removes session files that are “not needed” anymore. And as you may wonder, how the server knows whether a session file is not needed anymore.

Cleaning up the session files is a task for the session garbage collector (gc). Generally, the garbage collector runs during a session start. But not always. Every time a session starts the garbage collector runs with a probability that equals to the ratio of two configuration parameters: the session.gc_probability and the session.gc_divisor. The default values of these parameters are 1 and 100, respectively. So, by default, on a session start, the garbage collector will run with a probability of 1%.

There is also another configuration parameter named session.gc_maxlifetime , which defines the maximum lifetime of an unused session. The default value is 1440 seconds (24 minutes). In a few words, every time the garbage collector runs, it deletes the session file of every session that has not been used for 24 minutes, or more.

Since the garbage collector should check the status of every session, a high value for the run probability will add a significant load to the server. On the other hand, a very low value of this probability will lead to a very high number of session files, most of which will be useless.

If you want to see some tests on how the garbage collection can affect the performance of a web site, see:
http://inchoo.net/magento/programming-magento/session-storage-php/

Changing the session name: As we mentioned, the default name PHP sessions is the “PHPSESSID”. This name is used as the name of the relevant session cookie, which carries the session’s ID. We can change it through the session_name() function Again, as with session_set_cookie_params, the function should be called before session_start. This name change lasts only for the execution of the script. If session_name function is used without parameters, it just returns the currently used session name. If we pass as parameter another session name, then all sessions that will be created during the rest of script execution will be named based according to the new name.

If we change the session name after the session has been created for a user and call session_start, then a new session will created for this user and a second session cookie (based on the new name) will be set to the user’s browser. From this point on and in every request, the browser will send both session cookies. Which of these two cookies will be taken into account by the web server depends from the session_name call (or the absence of such a call).

Regenerate a session: Regenerate a sessions means change its session ID. This can be done with session_regenerate_id() function which creates a new session file with a different session ID and copies all session data from the current session to this file. Then, it sets the response headers appropriately in order to tell the browser to set a cookie for this new session. Since the name, path and domain for this cookie would be the same as for the session cookie that the browser already holds, the later one will be overwritten by the new one. The session_regenerate_id() function takes only one optional parameter that determines whether the old session file will be deleted or not. The default behavior is not to delete it.

We will see the reason in part 2 of this article, which is related to security and performance considerations for PHP sessions.