Sessions
In PHP, sessions are implemented using the $_SESSION
superglobal and the
session_start
and session_destroy
functions. (There are additional session
functions, but these two are the most frequently
used.) Sessions allow the server to persist application state across web pages.
One example of using sessions is user authentication. If a user has logged into a web site, then they should have access to resources without re-entering their credentials. Because HTTP cookies are managed by the client, they cannot be used for security-related data, such as whether or not the client has authenticated – the client could easily manipulate the cookie.1 Thus, sessions must be used when confidentiality, integrity, or availability must be guaranteed.
Let’s look at several examples related to authorization. Each example is illustrated with a state machine diagram and corresponding PHP code.
-
A login page for a website. As depicted in the state machine diagram, the login page starts (or resumes) a session. If the user’s inputs (username and password) match the stored account credentials, then the user is redirected to the directory index; otherwise, an error message is displayed for the invalid login.
The corresponding PHP code that implements this functionality is significantly longer, but not terribly complex. Note the following in the source code:
- If the user has already authenticated (signified by
$_SESSION["username"]
being set), then the user is automatically redirected to the directory index. - A prepared statement is used when querying the database to mitigate SQL injection vulnerabilities.
- PHP’s
password_verify
function is used to compare the provided password to the hash of the password retrieved from the database. A database should never store plain text passwords! (password_hash($password, PASSWORD_DEFAULT)
was used to create the original password hash.) - An error message is displayed when the user’s account credentials do not match an account. When that happens, the username is automatically entered in the corresponding input text field so the user need not re-type it.
<?php session_start(); // start (or resume) session // create database connection ($connection) $connection = new mysqli("localhost", "student", "CompSci364", "student"); $error = false; if (! isset($_SESSION["username"]) // already authenticated && isset($_POST["username"], $_POST["password"])) { // query database for account information $statement = $connection->prepare("SELECT password_hash ". "FROM Users ". "WHERE username = ?;"); $statement->bind_param("s", $_POST["username"]); $statement->execute(); $statement->bind_result($password_hash); // username present in database if ($statement->fetch()) { // verify that the password matches stored password hash if (password_verify($_POST["password"], $password_hash)) { // store the username to indicate authentication $_SESSION["username"] = $_POST["username"]; } } $error = true; } if (isset($_SESSION["username"])) { // authenticated $location = dirname($_SERVER["PHP_SELF"]); if (isset($_REQUEST["redirect"])) { $location = $_REQUEST["redirect"]; } // redirect to requested page header("Location: ".$location); } ?> <!DOCTYPE html> <html> <body> <?php if ($error) { echo "Invalid username or password."; } ?> <form action="<?php echo $_SERVER["PHP_SELF"]. "?".$_SERVER["QUERY_STRING"]; ?>" method="post"> <label for="username">Username</label> <input name="username" type="text" value="<?php if (isset($_POST["username"])) echo $_POST["username"]; ?>" /> <label for="password">Password</label> <input name="password" type="password" /> <input type="submit" value="Log in" /> </form> </body> </html>
- If the user has already authenticated (signified by
-
A protected web page. Most web applications require a user to authenticate before accessing resources. This functionality is easy to implement using an authentication script that is included on each page that does not allow public access.
In the following state machine diagram, the protected resource (
resource.php
) includes the authentication scriptauthenticate.php
. If the user has not authenticated, then the user is automatically redirected to the login page; otherwise, the script ends and the protected resource is served.The PHP code for the protected resource (
resource.php
) is simple:<?php include('authenticate.php') ?> <!DOCTYPE html> <html> <body> This resource is protected and should only be displayed after the user is authenticated. <a href="logout.php">Log out</a> </body> </html>
The authentication script is also straightforward. The major item of note is that it passes the URL of the requested resource (
$_SERVER["PHP_SELF"]
) to the login page so that the user is automatically redirected to that URL after authenticating.<?php session_start(); // start (or resume) session if (! isset($_SESSION["username"])) { header("Location: login.php?redirect=".$_SERVER["PHP_SELF"]); }
-
The logout script must simply delete all the data currently associated with the session. In PHP, the corresponding function is
session_destroy
.<?php session_start(); // start (or resume) session session_destroy();
That’s using PHP sessions to implement authentication in a nutshell!
-
Somewhat confusingly PHP tracks sessions using a session identifier, which is either stored in an HTTP cookie or encoded as a URL parameter (
PHPSESSID
). You might wonder how PHP sessions can be secure if the session identifier is stored insecurely (i.e., in an HTTP cookie). In essence, the session identifier allows the server to look up the corresponding session data, but that data is not stored by, or even sent to, the client. If the user modifies the session identifier, the server will assume that the session is invalid, and because the session identifier is essentially random, the probability of it being guessed – assuming that the site uses HTTPS to encrypt data being sent to / from the client – is essentially zero. ↩