12. Cookies and Sessions

In This Chapter

Making a Login Page 368

Making the Login Functions 371

Using Cookies 376

Using Sessions 388

Improving Session Security 396

Review and Pursue 400

The Hypertext Transfer Protocol (HTTP) is a stateless technology, meaning that each individual HTML page is an unrelated entity. HTTP has no method for tracking users or retaining variables as a person traverses a site. Without the server being able to track a user, there can be no shopping carts or custom Web-site personalization. Using a server-side technology like PHP, you can overcome the statelessness of the Web. The two best PHP tools for this purpose are cookies and sessions.

The key difference between cookies and sessions is that cookies store data in the user’s Web browser and sessions store data on the server itself. Sessions are generally more secure than cookies and can store much more information. As both technologies are easy to use with PHP and are worth knowing, this chapter covers both cookies and sessions. The examples for demonstrating this information will be a login system, based upon the existing sitename database.

Making a Login Page

A login process involves just a few components Image:

• A form for submitting the login information

• A validation routine that confirms the necessary information was submitted

• A database query that compares the submitted information against the stored information

• Cookies or sessions to store data that reflects a successful login

Image

Image The login process.

Subsequent pages can then have checks to confirm that the user is logged in (to limit access to that page or add features). There is also, of course, a logging-out process, which involves clearing out the cookies or session data that represent a logged-in status.

To start all this, let’s take some of these common elements and place them into separate files. Then, the pages that require this functionality can include the necessary files. Breaking up the logic this way will make some of the following scripts easier to read and write, plus cut down on their redundancies. You’ll define two includable files. This first script will contain the bulk of a login page, including the header, the error reporting, the form, and the footer Image.

Image

Image The login form and page.

Script 12.1. The login_page.inc.php script creates the complete login page, including the form, and reports any errors. It will be included by other pages that need to show the login page.


1   <?php # Script 12.1 - login_page.inc.php
2   // This page prints any errors
    associated with logging in
3   // and it creates the entire login page,
    including the form.
4
5   // Include the header:
6   $page_title = 'Login';
7   include ('includes/header.html'),
8
9   // Print any error messages, if they exist:
10  if (isset($errors) && !empty($errors)) {
11     echo '<h1>Error!</h1>
12     <p class="error">The following
       error(s) occurred:<br />';
13     foreach ($errors as $msg) {
14        echo " - $msg<br /> ";
15     }
16     echo '</p><p>Please try again.</p>';
17  }
18
19  // Display the form:
20  ?><h1>Login</h1>
21  <form action="login.php" method="post">
22     <p>Email Address: <input type="text"
       name="email" size="20" maxlength="60" />
       </p>
23     <p>Password: <input type="password"
       name="pass" size="20" maxlength="20" />
       </p>
24     <p><input type="submit" name="submit"
       value="Login" /></p>
25  </form>
26
27  <?php include ('includes/footer.html'),
?>


To make a login page

1. Begin a new PHP page in your text editor or IDE, to be named login_page.inc.php (Script 12.1):

<?php # Script 12.1 - login_page.inc.php

2. Include the header:

$page_title = 'Login';
include ('includes/header.html'),

This chapter will make use of the same template system first created in Chapter 3, “Creating Dynamic Web Sites,” then modified in Chapter 9, “Using PHP with MySQL.”

3. Print any error messages, if they exist:

if (isset($errors) && !empty($errors)) {
  echo '<h1>Error!</h1>
  <p class="error">The following error(s) occurred:<br />';
  foreach ($errors as $msg) {
    echo " - $msg<br /> ";
  }
  echo '</p><p>Please try again.</p>';
}

This code was also developed back in Chapter 9, although an additional isset( ) clause has been added as an extra precaution. If any errors exist (in the $errors array variable), they’ll be printed as an unordered list Image.

Image

Image As with other scripts in this book, form errors are displayed above the form itself.

4. Display the form:

?><h1>Login</h1>
<form action="login.php" method="post">
  <p>Email Address: <input type="text" name="email" size="20" maxlength="60" /> </p>
  <p>Password: <input type="password" name="pass" size="20" maxlength="20" /></p>
  <p><input type="submit" name="submit" value="Login" /></p>
</form>

The HTML form only needs two text inputs: one for an email address and a second for the password. The names of the inputs match those in the users table of the sitename database (which this login system is based upon).

To make it easier to create the HTML form, the PHP section is closed first. The form is not sticky, but you could easily add code to accomplish that.

5. Complete the page:

<?php include ('includes/footer.html'), ?>

6. Save the file as login_page.inc.php and place it in your Web directory (in the includes folder, along with the files from Chapter 3 and Chapter 9: header.html, footer.html, and style.css).

The page will use a .inc.php extension to indicate both that it’s an includable file and that it contains PHP code.


Tip

It may seem illogical that this script includes the header and footer file from within the includes directory when this script will also be within that same directory. This code works because this script will be included by pages within the main directory; thus the include references are with respect to the parent file, not this one.


Making the Login Functions

Along with the login page that was stored in login_page.inc.php, there’s a little bit of functionality that will be common to several scripts in this chapter. In this next script, also to be included by other pages in the login/logout system, two functions will be defined.

First, many pages will end up redirecting the user from one page to another. For example, upon successfully logging in, the user will be taken to loggedin.php. If a user accesses loggedin.php and they aren’t logged in, they should be taken to index.php. Redirection uses the header( ) function, introduced in Chapter 11, “Web Application Development.” The syntax for redirection is

header ('Location: http://www.example.com/page.php'),

Because this function will send the browser to page.php, the current script should be terminated using exit( ) immediately after this:

header ('Location: http://www.example.com/page.php'),
exit( );

If you don’t call exit( ), the current script will continue to run (just not in the Web browser).

The location value in the header( ) call should be an absolute URL (www.example.com/page.php instead of just page.php). You can hard-code this value into every header( ) call or, better yet, have PHP dynamically determine it. The first function in this next script will do just that, and then redirect the user to that absolute URL.

The other bit of code that will be used by multiple scripts in this chapter validates the login form. This is a three-step process:

1. Confirm that an email address was provided.

2. Confirm that a password was provided.

3. Confirm that the provided email address and password match those stored in the database (during the registration process).

This next script will define two different functions. The details of how each function works will be explained in the steps that follow.

To create the login functions

1. Begin a new PHP document in your text editor or IDE, to be named login_functions.inc.php (Script 12.2):

<?php # Script 12.2 - login_functions.inc.php

As this file will be included by other files, it does not need to contain any HTML.

2. Begin defining a new function:

function redirect_user ($page = 'index.php') {

The redirect_user( ) function will create an absolute URL that’s correct for the site running these scripts, and then redirect the user to that page. The benefit of doing this dynamically (as opposed to just hard-coding http://www.example.com/page.php) is that you can develop your code on one server (like your own computer) and then move it to another server without ever needing to change this code.

The function takes one optional argument: the final destination page name. The default value is index.php.

3. Start defining the URL:

$url = 'http://' . $_SERVER ['HTTP_HOST'] . dirname($_SERVER ['PHP_SELF']);

Script 12.2. The login_functions.inc.php script defines two functions that will be used by different scripts in the login/logout process.


1   <?php # Script 12.2 - login_functions.
    inc.php
2   // This page defines two functions used
    by the login/logout process.
3
4   /* This function determines an absolute
    URL and redirects the user there.
5    * The function takes one argument: the
    page to be redirected to.
6    * The argument defaults to index.php.
7    */
8   function redirect_user ($page = 'index.
    php') {
9
10     // Start defining the URL...
11     // URL is http:// plus the host name
       plus the current directory:
12     $url = 'http://' . $_SERVER['HTTP_
       HOST'] . dirname($_SERVER['PHP_SELF']);
13
14     // Remove any trailing slashes:
15     $url = rtrim($url, '/'),
16
17     // Add the page:
18     $url .= '/' . $page;
19
20     // Redirect the user:
21     header("Location: $url");
22     exit( ); // Quit the script.
23
24  } // End of redirect_user( ) function.
25
26
27  /* This function validates the form data
    (the email address and password).
28   * If both are present, the database is
    queried.
29   * The function requires a database
    connection.
30   * The function returns an array of
    information, including:
31   * - a TRUE/FALSE variable indicating
    success
32   * - an array of either errors or the
    database result
33   */
34  function check_login($dbc, $email = '',
    $pass = '') {
35
36     $errors = array( ); // Initialize
       error array.
37
38     // Validate the email address:
39     if (empty($email)) {
40        $errors[ ] = 'You forgot to enter
          your email address.';
41     } else {
42        $e = mysqli_real_escape_
          string($dbc, trim($email));
43     }
44
45     // Validate the password:
46     if (empty($pass)) {
47        $errors[ ] = 'You forgot to enter
          your password.';
48     } else {
49        $p = mysqli_real_escape_
          string($dbc, trim($pass));
50     }
51
52     if (empty($errors)) { // If
       everything's OK.
53
54        // Retrieve the user_id and
          first_name for that email/password
          combination:
55        $q = "SELECT user_id, first_name
          FROM users WHERE email='$e' AND
          pass=SHA1('$p')";
56        $r = @mysqli_query ($dbc, $q);
          // Run the query.
57
58        // Check the result:
59        if (mysqli_num_rows($r) = = 1) {
60
61           // Fetch the record:
62           $row = mysqli_fetch_array ($r,
             MYSQLI_ASSOC);
63
64           // Return true and the record:
65           return array(true, $row);
66
67        } else { // Not a match!
68           $errors[ ] = 'The email address
             and password entered do not
             match those on file.';
69        }
70
71     } // End of empty($errors) IF.
72
73     // Return false and the errors:
74     return array(false, $errors);
75
76  } // End of check_login( ) function.


To start, $url is assigned the value of http:// plus the host name (which could be either localhost or www.example.com). To this is added the name of the current directory using the dirname( ) function, in case the redirection is taking place within a subfolder. $_SERVER['PHP_SELF'] refers to the current script (which will be the one calling this function), including the directory name. That whole value might be /somedir/page.php. The dirname( ) function will return just the directory part from that value (i.e., /somedir/).

4. Remove any ending slashes from the URL:

$url = rtrim($url, '/'),

Because the existence of a subfolder might add an extra slash (/) or backslash (, for Windows), the function needs to remove that. To do so, apply the rtrim( ) function. By default, this function removes spaces from the right side of a string. If provided with a list of characters to remove as the second argument, it’ll chop those off instead. The characters to be removed are / and . But since the backslash is the escape character in PHP, you need to use \ to refer to a single backslash. With this one line of code, if $url concludes with either of these characters, the rtrim( ) function will remove them.

5. Append the specific page to the URL:

$url .= '/' . $page;

Next, the specific page name is concatenated to the $url. It’s preceded by a slash because any trailing slashes were removed in Step 4 and you can’t have www.example.compage.php as the URL.

This may all seem to be quite complicated, but it’s a very effective way to ensure that the redirection works no matter on what server, or from what directory, the script is being run (as long as the redirection is taking place within that directory).

6. Redirect the user and complete the function:

  header("Location: $url");
  exit( ); // Quit the script.
} // End of redirect_user( ) function.

The final steps are to send a Location header and terminate the execution of the script.

7. Begin a new function:

function check_login($dbc, $email = '', $pass = '') {

This function will validate the login information. It takes three arguments: the database connection, which is required; the email address, which is optional; and the password, which is also optional.

Although this function could access $_POST['email'] and $_POST['pass'] directly, it’s better if the function is passed these values, making the function more independent.

8. Validate the email address and password:

$errors = array( );
if (empty($email)) {
  $errors[ ] = 'You forgot to enter your email address.';
} else {
  $e = mysqli_real_escape_string ($dbc, trim($email));
}
if (empty($pass)) {
  $errors[ ] = 'You forgot to enter your password.';
} else {
  $p = mysqli_real_escape_string ($dbc, trim($pass));
}

This validation routine is similar to that used in the registration page. If any problems occur, they’ll be added to the $errors array, which will eventually be used on the login page (see Image under “Making a Login Page”). Note that this $errors array is local to the function. Even though it has the same name, this is not the same $errors variable that is used in the login page. Code later in the function will return this $errors variable’s value, and code in the scripts that call this function will then assign this returned value to the proper, global $errors array, usable on the login page.

9. If no errors occurred, run the database query:

if (empty($errors)) {
  $q = "SELECT user_id, first_name FROM users WHERE email='$e' AND pass=SHA1('$p')";
  $r = @mysqli_query ($dbc, $q);

The query selects the user_id and first_name values from the database where the submitted email address (from the form) matches the stored email address and the SHA1( ) version of the submitted password matches the stored password Image.

Image

Image The results of the login query, shown in the mysql client, if the user submitted the proper email address/password combination.

10. Check the results of the query:

if (mysqli_num_rows($r) = = 1) {
  $row = mysqli_fetch_array ($r, MYSQLI_ASSOC);
  return array(true, $row);

If the query returned one row, then the login information was correct. The results are then fetched into $row. The final step in a successful login is to return two pieces of information back to the requesting script: the Boolean true, indicating that the login was a success; and the data fetched from MySQL. Using the array( ) function, both the Boolean value and the $row array can be returned by this function.

11. If no record was selected by the query, create an error:

} else { // Not a match!
  $errors[ ] = 'The email address and password entered do not match those on file.';
}

If the query did not return one row, then an error message is added to the array. It will end up being displayed on the login page Image.

Image

Image If the user entered an email address and password, but they don’t match the values stored in the database, this is the result in the Web browser.

12. Complete the conditional begun in Step 9 and complete the function:

  } // End of empty($errors) IF.
  return array(false, $errors);
} // End of check_login( ) function.

The final step is for the function to return a value of false, indicating that login failed, and to return the $errors array, which stores the reason(s) for failure. This return statement can be placed here—at the end of the function instead of within a conditional—because the function will only get to this point if the login failed. If the login succeeded, the return line in Step 10 will stop the function from continuing (a function stops as soon as it executes a return).

13. Save the file as login_functions.inc.php and place it in your Web directory (in the includes folder, along with header.html, footer.html, and style.css).

This page will also use a .inc.php extension to indicate both that it’s an includable file and that it contains PHP code.

As with some other includable files created in this book (although not login_page.inc.php), the closing PHP tag—?>—is omitted. Doing so prevents potential complications that can arise should an includable file have an errant blank space or line after the closing tag.


Tip

The scripts in this chapter include no debugging code (like the MySQL error or query). If you have problems with these scripts, apply the debugging techniques outlined in Chapter 8, “Error Handling and Debugging.”



Tip

You can add name=value pairs to the URL in a header( ) call to pass values to the target page:

$url .= '?name=' . urlencode(value);


Using Cookies

Cookies are a way for a server to store information on the user’s machine. This is one way that a site can remember or track a user over the course of a visit. Think of a cookie as being like a name tag: you tell the server your name and it gives you a sticker to wear. Then it can know who you are by referring back to that name tag Image.

Image

Image How cookies are sent back and forth between the server and the client.

In this section, you will learn how to set a cookie, retrieve information from a stored cookie, alter a cookie’s settings, and then delete a cookie.

Setting cookies

The most important thing to understand about cookies is that they must be sent from the server to the client prior to any other information. Should the server attempt to send a cookie after the Web browser has already received HTML—even an extraneous white space—an error message will result and the cookie will not be sent Image. This is by far the most common cookie-related error but is easily fixed. If you see such a message:

1. Note the script and line number following output started at.

2. Open that script and head to that line number.

3. Remove the blank space, line, text, HTML, or whatever that is outputted by that line.

Image

Image The headers already sent... error message is all too common when creating cookies. Pay attention to what the error message says in order to find and fix the problem.

Cookies are sent via the setcookie( ) function:

setcookie (name, value);
setcookie ('name', 'Nicole'),

The second line of code will send a cookie to the browser with a name of name and a value of Nicole Image.

Image

Image If the browser is set to ask for permission when receiving cookies, you’ll see a message like this when a site attempts to send one (this is Firefox’s version of the prompt).

You can continue to send more cookies to the browser with subsequent uses of the setcookie( ) function:

setcookie ('ID', 263);
setcookie ('email', '[email protected]'),

As for the cookies name, it’s best not to use white spaces or punctuation, and pay attention to the exact case used.

To send a cookie

1. Begin a new PHP document in your text editor or IDE, to be named login.php (Script 12.3):

<?php # Script 12.3 - login.php

For this example, let’s make a login.php script that works in conjunction with the scripts from Chapter 9. This script will also require the two files created at the beginning of the chapter.

2. If the form has been submitted, include the two helper files:

if ($_SERVER['REQUEST_METHOD'] = = 'POST') {
  require ('includes/login_functions.inc.php'),
  require ('../mysqli_connect.php'),

This script will do two things: handle the form submission and display the form. This conditional checks for the submission.

Within the conditional, the script must include both login_functions.inc.php and mysqli_connect.php (which was created in Chapter 9 and should still be in the same location relative to this script; change your code here if your mysqli_connect.php is not in the parent directory of the current directory).

I’ve chosen to use require( ) in both cases, instead of include( ), because a failure to include either of these scripts makes the login process impossible.

Script 12.3. Upon a successful login, the login.php script creates two cookies and redirects the user.


1   <?php # Script 12.3 - login.php
2   // This page processes the login form
    submission.
3   // Upon successful login, the user is
    redirected.
4   // Two included files are necessary.
5   // Send NOTHING to the Web browser prior
    to the setcookie( ) lines!
6
7   // Check if the form has been submitted:
8   if ($_SERVER['REQUEST_METHOD'] = = 'POST') {
9
10     // For processing the login:
11     require ('includes/login_functions.
       inc.php'),
12
13     // Need the database connection:
14     require ('../mysqli_connect.php'),
15
16     // Check the login:
17     list ($check, $data) = check_login($dbc,
       $_POST['email'], $_POST['pass']);
18
19     if ($check) { // OK!
20
21        // Set the cookies:
22        setcookie ('user_id', $data
          ['user_id']);
23        setcookie ('first_name',
          $data['first_name']);
24
25        // Redirect:
26        redirect_user('loggedin.php'),
27
28     } else { // Unsuccessful!
29
30        // Assign $data to $errors for
          error reporting
31        // in the login_page.inc.php file.
32        $errors = $data;
33
34     }
35
36     mysqli_close($dbc); // Close the
       database connection.
37
38  } // End of the main submit conditional.
39
40  // Create the page:
41  include ('includes/login_page.inc.php'),
42  ?>


3. Validate the form data:

list ($check, $data) = check_login ($dbc, $_POST['email'], $_POST['pass']);

After including both files, the check_login( ) function can be called. It’s passed the database connection (which comes from mysqli_connect.php), along with the email address and the password (both of which come from the form). As an added precaution, the script could confirm that both variables are set and not empty prior to invoking the function.

This function returns an array of two elements: a Boolean value and an array (of user data or errors). To assign those returned values to variables, apply the list( ) function. The first value returned by the function (the Boolean) will be assigned to $check. The second value returned (either the $row or $errors array) will be assigned to $data.

4. If the user entered the correct information, log them in:

if ($check) { // OK!
  setcookie ('user_id', $data['user_id']);
  setcookie ('first_name', $data['first_name']);

The $check variable indicates the success of the login attempt. If it has a TRUE value, then $data contains the user’s ID and first name. These two values can be used in cookies.

Generally speaking, you should never store a database table’s primary key value, such as $data['user_id'], in a cookie, because cookies can be manipulated easily. In this situation, it’s not going to be a problem as the user_id value isn’t actually used anywhere in the site (it’s being stored in the cookie for demonstration purposes).

5. Redirect the user to another page:

redirect_user('loggedin.php'),

Using the function defined earlier in the chapter, the user will be redirected to another script upon a successful login. The specific page to be redirected to is loggedin.php.

6. Complete the $check conditional (started in Step 4) and then close the database connection:

} else {
  $errors = $data;
}
mysqli_close($dbc);

If $check has a FALSE value, then the $data variable is storing the errors generated within the check_login( ) function. If so, the errors should be assigned to the $errors variable, because that’s what the code in the script that displays the login page—login_page.inc.php—is expecting.

7. Complete the main submit conditional and include the login page:

}
include ('includes/login_page.inc.php'),
?>

This login.php script itself primarily performs validation, by calling the check_login( ) function, and handles the cookies and redirection. The login_page.inc.php file contains the login page itself, so it just needs to be included.

8. Save the file as login.php, place it in your Web directory (in the same folder as the files from Chapter 9), and load this page in your Web browser (see Image under “Making a Login Page”).

If you want, you can submit the form erroneously, but you cannot correctly log in yet, as the final destination—loggedin.php—hasn’t been written.


Tip

Cookies are limited to about 4 KB of total data, and each Web browser can remember a limited number of cookies from any one site. This limit is 50 cookies for most of the current Web browsers (but if you’re sending out 50 different cookies, you may want to rethink how you do things).



Tip

The setcookie( ) function is one of the few functions in PHP that could have different results in different browsers, since each browser treats cookies in its own way. Be sure to test your Web sites in multiple browsers on different platforms to ensure consistency.



Tip

If the first two included files send anything to the Web browser or even have blank lines or spaces after the closing PHP tag, you’ll see a headers already sent error. This is why neither includes the terminating PHP tag.


Accessing cookies

To retrieve a value from a cookie, you only need to refer to the $_COOKIE superglobal, using the appropriate cookie name as the key (as you would with any array). For example, to retrieve the value of the cookie established with the line

setcookie ('username', 'Trout'),

you would refer to $_COOKIE['username']. In the following example, the cookies set by the login.php script will be accessed in two ways. First, a check will be made that the user is logged in (otherwise, they shouldn’t be accessing this page). Second, the user will be greeted by their first name, which was stored in a cookie.

To access a cookie

1. Begin a new PHP document in your text editor or IDE, to be named loggedin.php (Script 12.4):

<?php # Script 12.4 - loggedin.php

The user will be redirected to this page after successfully logging in. The script will greet the user by first name, using the cookie.

2. Check for the presence of a cookie.

if (!isset($_COOKIE['user_id'])) {

Since a user shouldn’t be able to access this page unless they are logged in, check for a cookie that should have been set (in login.php).

3. Redirect the user if they are not logged in:

  require ('includes/login_functions.inc.php'),
  redirect_user( );
}

If the user is not logged in, they will be automatically redirected to the main page. This is a simple way to limit access to content.

4. Include the page header:

$page_title = 'Logged In!';
include ('includes/header.html'),

Script 12.4. The loggedin.php script prints a greeting to a user thanks to a stored cookie.


1   <?php # Script 12.4 - loggedin.php
2   // The user is redirected here from
    login.php.
3
4   // If no cookie is present, redirect the
    user:
5   if (!isset($_COOKIE['user_id'])) {
6
7      // Need the functions:
8      require ('includes/login_functions.
       inc.php'),
9      redirect_user( );
10
11  }
12
13  // Set the page title and include the
    HTML header:
14  $page_title = 'Logged In!';
15  include ('includes/header.html'),
16
17  // Print a customized message:
18  echo "<h1>Logged In!</h1>
19  <p>You are now logged in, {$_COOKIE
    ['first_name']}!</p>
20  <p><a href="logout.php">Logout</a></p>";
21
22  include ('includes/footer.html'),
23  ?>


5. Welcome the user, referencing the cookie:

echo "<h1>Logged In!</h1>
<p>You are now logged in, {$_COOKIE['first_name']}!</p>
<p><a href="logout.php">Logout </a></p>";

To greet the user by name, refer to the $_COOKIE['first_name'] variable (enclosed within curly braces to avoid parse errors). A link to the logout page (to be written later in the chapter) is also printed.

6. Complete the HTML page:

include ('includes/footer.html'),
?>

7. Save the file as loggedin.php, place it in your Web directory (in the same folder as login.php), and test it in your Web browser by logging in through login.php Image.

Image

Image If you used the correct email address and password, you’ll see this page after logging in.

Since these examples use the same database as those in Chapter 9, you should be able to log in using the registered username and password submitted at that time.

8. To see the cookies being set Image and Image, change the cookie settings for your browser and test again.

Image

Image The user_id cookie with a value of 1.

Image

Image The first_name cookie with a value of Larry (yours will probably be different).


Tip

Some browsers (e.g., Internet Explorer) will not adhere to your cookie-prompting preferences for cookies sent over localhost.



Tip

A cookie is not accessible until the setting page (e.g., login.php) has been reloaded or another page has been accessed (in other words, you cannot set and access a cookie in the same page).



Tip

If users decline a cookie or have their Web browser set not to accept them, they will automatically be redirected to the home page in this example, even if they successfully logged in. For this reason, you may want to let the user know that cookies are required.


Setting cookie parameters

Although passing just the name and value arguments to the setcookie( ) function will suffice, you ought to be aware of the other arguments available. The function can take up to five more parameters, each of which will alter the definition of the cookie.

setcookie (name, value, expiration, path, host, secure, httponly);

The expiration argument is used to set a definitive length of time for a cookie to exist, specified in seconds since the epoch (the epoch is midnight on January 1, 1970). If it is not set or if it’s set to a value of 0, the cookie will continue to be functional until the user closes their browser. These cookies are said to last for the browser session (also indicated in Image and Image).

To set a specific expiration time, add a number of minutes or hours to the current moment, retrieved using the time( ) function. The following line will set the expiration time of the cookie to be 30 minutes (60 seconds times 30 minutes) from the current moment:

setcookie (name, value, time( )+1800);

The path and host arguments are used to limit a cookie to a specific folder within a Web site (the path) or to a specific host (www.example.com or 192.168.0.1). For example, you could restrict a cookie to exist only while a user is within the admin folder of a domain (and the admin folder’s subfolders):

setcookie (name, value, expire, '/admin/'),

Setting the path to / will make the cookie visible within an entire domain (Web site). Setting the domain to .example.com will make the cookie visible within an entire domain and every subdomain (www.example.com, admin.example.com, pages.example.com, etc.).

Script 12.5. The login.php script now uses every argument the setcookie( ) function can take.


1   <?php # Script 12.5 - login.php #2
2   // This page processes the login form
    submission.
3   // The script now adds extra parameters
    to the setcookie( ) lines.
4
5   // Check if the form has been submitted:
6   if ($_SERVER['REQUEST_METHOD'] = = 'POST')
{
7
8      // Need two helper files:
9      require ('includes/login_functions.
       inc.php'),
10     require ('../mysqli_connect.php'),
11
12     // Check the login:
13     list ($check, $data) = check_login
       ($dbc, $_POST['email'], $_POST['pass']);
14
15     if ($check) { // OK!
16
17        // Set the cookies:
18        setcookie ('user_id', $data
          ['user_id'], time( )+3600,
          '/', '', 0, 0);
19        setcookie ('first_name', $data
          ['first_name'], time( )+3600,
          '/', '', 0, 0);
20
21        // Redirect:
22        redirect_user('loggedin.php'),
23
24     } else { // Unsuccessful!
25
26        // Assign $data to $errors for
          login_page.inc.php:
27        $errors = $data;
28
29     }
30
31     mysqli_close($dbc); // Close the
       database connection.
32
33  } // End of the main submit conditional.
34
35  // Create the page:
36  include ('includes/login_page.inc.php'),
37  ?>


The secure value dictates that a cookie should only be sent over a secure HTTPS connection. A 1 indicates that a secure connection must be used, and a 0 says that a standard connection is fine.

setcookie (name, value, expire, path, host, 1);

If your site is using a secure connection, you ought to restrict any cookies to HTTPS as well.

Finally, added in PHP 5.2 is the httponly argument. A Boolean value is used to make the cookie only accessible through HTTP (and HTTPS). Enforcing this restriction will make the cookie more secure (preventing some hack attempts) but is not supported by all browsers at the time of this writing.

setcookie (name, value, expire, path, host, secure, TRUE);

As with all functions that take arguments, you must pass the setcookie( ) values in order. To skip any parameter, use NULL, 0, or an empty string (don’t use FALSE). The expiration and secure values are both integers and are therefore not quoted.

To demonstrate this information, let’s add an expiration setting to the login cookies so that they last for only one hour.

To set a cookie’s parameters

1. Open login.php in your text editor (refer to Script 12.3), if it is not already.

2. Change the two setcookie( ) lines to include an expiration date that’s 60 minutes away (Script 12.5):

setcookie ('user_id', $data['user_id'], time( )+3600, '/', '', 0, 0);
setcookie ('first_name', $data['first_name'], time( )+3600, '/', '', 0, 0);

With the expiration date set to time( ) + 3600 (60 minutes times 60 seconds), the cookie will continue to exist for an hour after it is set. While making this change, every other parameter is explicitly addressed.

For the final parameter, which accepts a Boolean value, you can also use 0 to represent FALSE (PHP will handle the conversion for you). Doing so is a good idea, as using false in any of the cookie arguments can cause problems.

3. Save the script, place it in your Web directory, and test it in your Web browser by logging in Image.

Image

Image Changes to the setcookie( ) parameters, like an expiration date and time, will be reflected in the cookie sent to the Web browser (compare with Image).


Tip

Some browsers have difficulties with cookies that do not list every argument. Explicitly stating every parameter—even as an empty string—will achieve more reliable results across all browsers.



Tip

Here are some general guidelines for cookie expirations: If the cookie should last as long as the user’s session, do not set an expiration time; if the cookie should continue to exist after the user has closed and reopened his or her browser, set an expiration time weeks or months ahead; and if the cookie can constitute a security risk, set an expiration time of an hour or fraction thereof so that the cookie does not continue to exist too long after a user has left his or her browser.



Tip

For security purposes, you could set a 5- or 10-minute expiration time on a cookie and have the cookie resent with every new page the user visits (assuming that the cookie exists). This way, the cookie will continue to persist as long as the user is active but will automatically die 5 or 10 minutes after the user’s last action.



Tip

E-commerce and other privacy-related Web applications should use an SSL (Secure Sockets Layer) connection for all transactions, including the cookie.



Tip

Be careful with cookies created by scripts within a directory. If the path isn’t specified, then that cookie will only be available to other scripts within that same directory.


Deleting cookies

The final thing to understand about using cookies is how to delete one. While a cookie will automatically expire when the user’s browser is closed or when the expiration date/time is met, often you’ll want to manually delete the cookie instead. For example, in Web sites that have login capabilities, you will want to delete any cookies when the user logs out.

Although the setcookie( ) function can take up to seven arguments, only one is actually required—the cookie name. If you send a cookie that consists of a name without a value, it will have the same effect as deleting the existing cookie of the same name. For example, to create the cookie first_name, you use this line:

setcookie('first_name', 'Tyler'),

To delete the first_name cookie, you would code:

setcookie('first_name'),

As an added precaution, you can also set an expiration date that’s in the past:

setcookie('first_name', '', time( )-3600);

To demonstrate all of this, let’s add a logout capability to the site. The link to the logout page appears on loggedin.php.

As an added feature, the header file will be altered so that a Logout link appears when the user is logged in and a Login link appears when the user is logged out.

To delete a cookie

1. Begin a new PHP document in your text editor or IDE, to be named logout.php (Script 12.6):

<?php # Script 12.6 - logout.php

2. Check for the existence of a user_id cookie; if it is not present, redirect the user:

if (!isset($_COOKIE['user_id'])) {
  require ('includes/login_functions.inc.php'),
  redirect_user( );

Script 12.6. The logout.php script deletes the previously established cookies.


1   <?php # Script 12.6 - logout.php
2   // This page lets the user logout.
3
4   // If no cookie is present, redirect the
    user:
5   if (!isset($_COOKIE['user_id'])) {
6
7      // Need the function:
8      require ('includes/login_functions.
       inc.php'),
9      redirect_user( );
10
11  } else { // Delete the cookies:
12     setcookie ('user_id', '', time( )-3600,
       '/', '', 0, 0);
13     setcookie ('first_name', '',
       time( )-3600, '/', '', 0, 0);
14  }
15
16  // Set the page title and include the
    HTML header:
17  $page_title = 'Logged Out!';
18  include ('includes/header.html'),
19
20  // Print a customized message:
21  echo "<h1>Logged Out!</h1>
22  <p>You are now logged out, {$_COOKIE
    ['first_name']}!</p>";
23
24  include ('includes/footer.html'),
25  ?>


As with loggedin.php, if the user is not already logged in, this page should redirect the user to the home page. There’s no point in trying to log out a user who isn’t logged in!

3. Delete the cookies, if they exist:

} else {
  setcookie ('user_id', '', time( )-3600, '/', '', 0, 0);
  setcookie ('first_name', '', time( )-3600, '/', '', 0, 0);
}

If the user is logged in, these two cookies will effectively delete the existing ones. Except for the value and the expiration, the other arguments should have the same values as they do when the cookies were created.

4. Make the remainder of the PHP page:

$page_title = 'Logged Out!';
include ('includes/header.html'),
echo "<h1>Logged Out!</h1>
<p>You are now logged out, {$_COOKIE['first_name']}!</p>";
include ('includes/footer.html'),
?>

The page itself is also much like the loggedin.php page. Although it may seem odd that you can still refer to the first_name cookie (that was just deleted in this script), it makes perfect sense considering the process:

A. This page is requested by the client.

B. The server reads the available cookies from the client’s browser.

C. The page is run and does its thing (including sending new cookies that delete the existing ones).

Thus, in short, the original first_name cookie data is available to this script when it first runs. The set of cookies sent by this page (the delete cookies) aren’t available to this page, so the original values are still usable.

5. Save the file as logout.php and place it in your Web directory (in the same folder as login.php).

To create the logout link

1. Open header.html (refer to Script 9.1) in your text editor or IDE.

2. Change the fifth and final link to (Script 12.7):

<li><?php
if ( (isset($_COOKIE['user_id'])) && (basename($_SERVER['PHP_SELF']) != 'logout.php') ) {
  echo '<a href="logout.php"> Logout</a>';
} else {
  echo '<a href="login.php"> Login</a>';
}
?></li>

Instead of having a permanent login link in the navigation area, it should display a Login link if the user is not logged in Image or a Logout link if the user is Image. The preceding conditional will accomplish just that, depending upon the presence of a cookie.

Image

Image The home page with a Login link.

Image

Image After the user logs in, the page now has a Logout link.

Script 12.7. The header.html file now displays either a Login or a Logout link, depending upon the user’s current status.


1   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML
    1.0 Strict//EN" "http://www.w3.org/TR/
    xhtml1/DTD/xhtml1-strict.dtd">
2   <html xmlns="http://www.w3.org/1999/
    xhtml">
3   <head">
4      <title><?php echo $page_title; ?>
       </title>
5      <link rel="stylesheet" href="includes/
       style.css" type="text/css"
       media="screen" />
6      <meta http-equiv="content-type"
       content="text/html; charset=utf-8" />
7   </head>
8   <body>
9      <div id="header">
10        <h1>Your Website</h1>
11        <h2>catchy slogan...</h2>
12     </div>
13     <div id="navigation">
14        <ul>
15           <li><a href="index.php">Home
             Page</a></li>
16           <li><a href="register.
             php">Register</a></li>
17           <li><a href="view_users.
             php">View Users</a></li>
18           <li><a href="password.
             php">Change Password</a></li>
19           <li><?php // Create a login/
             logout link:
20  if ( (isset($_COOKIE['user_id']))
    && (basename($_SERVER['PHP_SELF'])
    != 'logout.php') ) {
21     echo '<a href="logout.php">Logout
       </a>';
22  } else {
23     echo '<a href="login.php">Login</a>';
24  }
25  ?></li>
26        </ul>
27     </div>
28     <div id="content"><!-- Start of the
       page-specific content. -->
29  <!-- Script 12.7 - header.html -->


For that condition, if the cookie is set, the user is logged in and can be shown the logout link. If the cookie is not set, the user should be shown the login link. There is one catch, however: Because the logout.php script would ordinarily display a logout link (because the cookie exists when the page is first being viewed), the conditional has to also check that the current page is not the logout.php script. An easy way to dynamically determine the current page is to apply the basename( ) function to $_SERVER['PHP_SELF'].

3. Save the file, place it in your Web directory (within the includes folder), and test the login/logout process in your Web browser Image.

Image

Image The result after logging out.


Tip

To see the result of the setcookie( ) calls in the logout.php script, turn on cookie prompting in your browser Image.

Image

Image This is how the deletion cookie appears in a Firefox prompt (compare with Image).



Tip

Due to a bug in how Internet Explorer on Windows handles cookies, you may need to set the host parameter to false (without quotes) in order to get the logout process to work when developing on your own computer (i.e., through localhost).



Tip

When deleting a cookie, you should always use the same parameters that set the cookie (aside from the value and expiration, naturally). If you set the host and path in the creation cookie, use them again in the deletion cookie.



Tip

To hammer the point home, remember that the deletion of a cookie does not take effect until the page has been reloaded or another page has been accessed. In other words, the cookie will still be available to a page after that page has deleted it.


Using Sessions

Another method of making data available to multiple pages of a Web site is to use sessions. The premise of a session is that data is stored on the server, not in the Web browser, and a session identifier is used to locate a particular user’s record (the session data). This session identifier is normally stored in the user’s Web browser via a cookie, but the sensitive data itself—like the user’s ID, name, and so on—always remains on the server.

The question may arise: why use sessions at all when cookies work just fine? First of all, sessions are likely more secure in that all of the recorded information is stored on the server and not continually sent back and forth between the server and the client. Second, you can store more data in a session. Third, some users reject cookies or turn them off completely. Sessions, while designed to work with a cookie, can function without them, too.

To demonstrate sessions—and to compare them with cookies—let’s rewrite the previous set of scripts.

Setting session variables

The most important rule with respect to sessions is that each page that will use them must begin by calling the session_start( ) function. This function tells PHP to either begin a new session or access an existing one. This function must be called before anything is sent to the Web browser!

The first time this function is used, session_start( ) will attempt to send a cookie with a name of PHPSESSID (the default session name) and a value of something like a61f8670baa8e90a30c878df89a2074b (32 hexadecimal letters, the session ID). Because of this attempt to send a cookie, session_start( ) must be called before any data is sent to the Web browser, as is the case when using the setcookie( ) and header( ) functions.

Script 12.8. This version of the login.php script uses sessions instead of cookies.


1   <?php # Script 12.8 - login.php #3
2   // This page processes the login form
    submission.
3   // The script now uses sessions.
4
5   // Check if the form has been submitted:
6   if ($_SERVER['REQUEST_METHOD'] = = 'POST') {
7
8      // Need two helper files:
9      require ('includes/login_functions.
       inc.php'),
10     require ('../mysqli_connect.php'),
11
12     // Check the login:
13     list ($check, $data) = check_login($dbc,
       $_POST['email'], $_POST['pass']);
14
15     if ($check) { // OK!
16
17        // Set the session data:
18        session_start( );
19        $_SESSION['user_id'] =
          $data'user_id'];
20        $_SESSION['first_name'] =
          $data['first_name'];
21
22        // Redirect:
23        redirect_user('loggedin.php'),
24
25     } else { // Unsuccessful!
26
27        // Assign $data to $errors for
          login_page.inc.php:
28        $errors = $data;
29
30     }
31
32     mysqli_close($dbc); // Close the
       database connection.
33
34  } // End of the main submit conditional.
35
36  // Create the page:
37  include ('includes/login_page.inc.php'),
38  ?>


Once the session has been started, values can be registered to the session using the normal array syntax, using the $_SESSION superglobal:

$_SESSION['key'] = value;
$_SESSION['name'] = 'Roxanne';
$_SESSION['id'] = 48;

Let’s update the login.php script with this in mind.

To begin a session

1. Open login.php (refer to Script 12.5) in your text editor or IDE.

2. Replace the setcookie( ) lines (18–19) with these lines (Script 12.8):

session_start( );
$_SESSION['user_id'] =
$data['user_id'];
$_SESSION['first_name'] =
$data['first_name'];

The first step is to begin the session. Since there are no echo statements, inclusions of HTML files, or even blank spaces prior to this point in the script, it will be safe to use session_start( ) at this point in the script (although the function call could be placed at the top of the script as well). Then, two key-value pairs are added to the $_SESSION superglobal array to register the user’s first name and user ID to the session.

3. Save the page as login.php, place it in your Web directory, and test it in your Web browser Image.

Image

Image The login form remains unchanged to the end user, but the underlying functionality now uses sessions.

Although loggedin.php and the header and script will need to be rewritten, you can still test the login script and see the resulting cookie Image. The loggedin.php page should redirect you back to the home page, though, as it’s still checking for the presence of a $_COOKIE variable.

Image

Image This cookie, created by PHP’s session_start( ) function, stores the session ID in the user’s browser.


Tip

Because sessions will normally send and read cookies, you should always try to begin them as early in the script as possible. Doing so will help you avoid the problem of attempting to send a cookie after the headers (HTML or white space) have already been sent.



Tip

If you want, you can set session.auto_start in the php.ini file to 1, making it unnecessary to use session_start( ) on each page. This does put a greater toll on the server and, for that reason, shouldn’t be used without some consideration of the circumstances.



Tip

You can store arrays in sessions (making $_SESSION a multidimensional array), just as you can store strings or numbers.


Accessing session variables

Once a session has been started and variables have been registered to it, you can create other scripts that will access those variables. To do so, each script must first enable sessions, again using session_start( ).

This function will give the current script access to the previously started session (if it can read the PHPSESSID value stored in the cookie) or create a new session if it cannot. Understand that if the current session ID cannot be found and a new session ID is generated, none of the data stored under the old session ID will be available. I mention this here and now because if you’re having problems with sessions, checking the session ID value to see if it changes from one page to the next is the first debugging step.

Assuming that there was no problem accessing the current session, to then refer to a session variable, use $_SESSION['var'], as you would refer to any other array.

Script 12.9. The loggedin.php script is updated so that it refers to $_SESSION and not $_COOKIE (changes are required on two lines).


1   <?php # Script 12.9 - loggedin.php #2
2   // The user is redirected here from
    login.php.
3
4   session_start( ); // Start the session.
5
6   // If no session value is present,
    redirect the user:
7   if (!isset($_SESSION['user_id'])) {
8
9      // Need the functions:
10     require ('includes/login_functions.
       inc.php'),
11     redirect_user( );
12
13  }
14
15  // Set the page title and include the
    HTML header:
16  $page_title = 'Logged In!';
17  include ('includes/header.html'),
18
19  // Print a customized message:
20  echo "<h1>Logged In!</h1>
21  <p>You are now logged in, {$_SESSION
    ['first_name']}!</p>
22  <p><a href="logout.php">Logout</a></p>";
23
24  include ('includes/footer.html'),
25  ?>


To access session variables

1. Open loggedin.php (refer to Script 12.4) in your text editor or IDE.

2. Add a call to the session_start( ) function (Script 12.9):

session_start( );

Every PHP script that either sets or accesses session variables must use the session_start( ) function. This line must be called before the header.html file is included and before anything is sent to the Web browser.

3. Replace the references to $_COOKIE with $_SESSION (lines 5 and 19 of the original file):

if (!isset($_SESSION['user_id'])) {

and

echo "<h1>Logged In!</h1>
<p>You are now logged in, {$_SESSION['first_name']}!</p>
<p><a href="logout.php">Logout </a></p>";

Switching a script from cookies to sessions requires only that you change uses of $_COOKIE to $_SESSION (assuming that the same names were used).

4. Save the file as loggedin.php, place it in your Web directory, and test it in your browser Image.

Image

Image After logging in, the user is redirected to loggedin.php, which will welcome the user by name using the stored session value.

5. Replace the reference to $_COOKIE with $_SESSION in header.html (from Script 12.7 to Script 12.10):

if (isset($_SESSION['user_id'])) {

For the Login/Logout links to function properly (notice the incorrect link in Image), the reference to the cookie variable within the header file must be switched over to sessions. The header file does not need to call the session_start( ) function, as it’ll be included by pages that do.

Note that this conditional does not need to check if the current page is the logout page, as session data behaves differently than cookie data (I’ll explain this further in the next section of the chapter).

6. Save the header file, place it in your Web directory (in the includes folder), and test it in your browser Image.

Image

Image With the header file altered for sessions, the proper Login/Logout links will be displayed (compare with Image).


Tip

For the Login/Logout links to work on the other pages (register.php, index.php, etc.), you’ll need to add the session_start( ) command to each of those.



Tip

As a reminder of what I already said, if you have an application where the session data does not seem to be accessible from one page to the next, it could be because a new session is being created on each page. To check for this, compare the session ID (the last few characters of the value will suffice) to see if it is the same. You can see the session’s ID by viewing the session cookie as it is sent or by invoking the session_id( ) function:

echo session_id( );



Tip

Session variables are available as soon as you’ve established them. So, unlike when using cookies, you can assign a value to $_SESSION['var'] and then refer to $_SESSION['var'] later in that same script.


Script 12.10. The header.html file now also references $_SESSION instead of $_COOKIE.


1   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML
    1.0 Strict//EN" "http://www.w3.org/TR/
    xhtml1/DTD/xhtml1-strict.dtd">
2   <html xmlns="http://www.w3.org/1999/xhtml">
3   <head">
4      <title><?php echo $page_title; ?>
       </title>
5      <link rel="stylesheet" href="includes/
       style.css" type="text/css"
       media="screen" />
6      <meta http-equiv="content-type"
       content="text/html; charset=utf-8" />
7   </head>
8   <body>
9      <div id="header">
10        <h1>Your Website</h1>
11        <h2>catchy slogan...</h2>
12     </div>
13     <div id="navigation">
14        <ul>
15           <li><a href="index.php">Home
             Page</a></li>
16           <li><a href="register.php">
             Register</a></li>
17           <li><a href="view_users.php">
             View Users</a></li>
18           <li><a href="password.php">
             Change Password</a></li>
19           <li><?php // Create a login/
             logout link:
20  if (isset($_SESSION['user_id'])) {
21     echo '<a href="logout.php">Logout</a>';
22  } else {
23     echo '<a href="login.php">Login</a>';
24  }
25  ?></li>
26        </ul>
27     </div>
28     <div id="content"><!-- Start of the
       page-specific content. -->
29  <!-- Script 12.10 - header.html -->


Script 12.11. Destroying a session, as you would in a logout page, requires special syntax to delete the session cookie and the session data on the server, as well as to clear out the $_SESSION array.


1   <?php # Script 12.11 - logout.php #2
2   // This page lets the user logout.
3   // This version uses sessions.
4
5   session_start( ); // Access the
    existing session.
6
7   // If no session variable exists,
    redirect the user:
8   if (!isset($_SESSION['user_id'])) {
9
10     // Need the functions:
11     require ('includes/login_functions.
       inc.php'),
12     redirect_user( );
13
14  } else { // Cancel the session:
15
16     $_SESSION = array( ); // Clear the
       variables.
17     session_destroy( ); // Destroy the
       session itself.
18     setcookie ('PHPSESSID', '', time( )-3600,
       '/', '', 0, 0); // Destroy the cookie.
19
20  }
21
22  // Set the page title and include the
    HTML header:
23  $page_title = 'Logged Out!';
24  include ('includes/header.html'),
25
26  // Print a customized message:
27  echo "<h1>Logged Out!</h1>
28  <p>You are now logged out!</p>";
29
30  include ('includes/footer.html'),
31  ?>


Deleting session variables

When using sessions, you ought to create a method of deleting the session data. In the current example, this would be necessary when the user logs out.

Whereas a cookie system only requires that another cookie be sent to destroy the existing cookie, sessions are slightly more demanding, since there are both the cookie on the client and the data on the server to consider.

To delete an individual session variable, you can use the unset( ) function (which works with any variable in PHP):

unset($_SESSION['var']);

But to delete every session variable, you shouldn’t use unset( ), instead, reset the $_SESSION array:

$_SESSION = array( );

Finally, to remove all of the session data from the server, call session_destroy( ):

session_destroy( );

Note that prior to using any of these methods, the page must begin with session_start( ) so that the existing session is accessed. Let’s update the logout.php script to clean out the session data.

To delete a session

1. Open logout.php (Script 12.6) in your text editor or IDE.

2. Immediately after the opening PHP line, start the session (Script 12.11):

session_start( );

Anytime you are using sessions, you must call the session_start( ) function, preferably at the very beginning of a page. This is true even if you are deleting a session.

3. Change the conditional so that it checks for the presence of a session variable:

if (!isset($_SESSION['user_id'])) {

As with the logout.php script in the cookie examples, if the user is not currently logged in, they will be redirected.

4. Replace the setcookie( ) lines (that delete the cookies) with:

$_SESSION = array( );
session_destroy( );
setcookie ('PHPSESSID', '', time( )-3600, '/', '', 0, 0);

The first line here will reset the entire $_SESSION variable as a new array, erasing its existing values. The second line removes the data from the server, and the third sends a cookie to delete the existing session cookie in the browser.

5. Remove the reference to $_COOKIE in the message:

echo "<h1>Logged Out!</h1>
<p>You are now logged out!</p>";

Unlike when using the cookie version of the logout.php script, you cannot refer to the user by their first name anymore, as all of that data has been deleted.

6. Save the file as logout.php, place it in your Web directory, and test it in your browser Image.

Image

Image The logout page (now featuring sessions).


Tip

The header.html file only needs to check if $_SESSION['user_id'] is set, and not if the page is the logout page, because by the time the header file is included by logout.php, all of the session data will have already been destroyed. The destruction of session data applies immediately, unlike with cookies.



Tip

Never set $_SESSION equal to NULL and never use unset($_SESSION). Either could cause problems on some servers.



Tip

In case it’s not absolutely clear what’s going on, there exists three kinds of information with a session: the session identifier (which is stored in a cookie by default), the session data (which is stored in a text file on the server), and the $_SESSION array (which is how a script accesses the session data in the text file). Just deleting the cookie doesn’t remove the data file and vice versa. Clearing out the $_SESSION array would erase the data from the text file, but the file itself would still exist, as would the cookie. The three steps outlined in this logout script effectively remove all traces of the session.


Improving Session Security

Because important information is normally stored in a session (you should never store sensitive data in a cookie), security becomes more of an issue. With sessions there are two things to pay attention to: the session ID, which is a reference point to the session data, and the session data itself, stored on the server. A malicious person is far more likely to hack into a session through the session ID than the data on the server, so I’ll focus on that side of things here (in the tips at the end of this section I mention two ways to protect the session data itself).

The session ID is the key to the session data. By default, PHP will store this in a cookie, which is preferable from a security standpoint. It is possible in PHP to use sessions without cookies, but that leaves the application vulnerable to session hijacking: If malicious user Alice can learn user Bob’s session ID, Alice can easily trick a server into thinking that Bob’s session ID is also Alice’s session ID. At that point, Alice would be riding coattails on Bob’s session and would have access to Bob’s data. Storing the session ID in a cookie makes it somewhat harder to steal.

Script 12.12. This final version of the login.php script also stores an encrypted form of the user’s HTTP_USER_AGENT (the browser and operating system of the client) in a session.


1   <?php # Script 12.12 - login.php #4
2   // This page processes the login form
    submission.
3   // The script now stores the HTTP_USER_
    AGENT value for added security.
4
5   // Check if the form has been submitted:
6   if ($_SERVER['REQUEST_METHOD'] = = 'POST')
{
7
8      // Need two helper files:
9      require ('includes/login_functions.
       inc.php'),
10     require ('../mysqli_connect.php'),
11
12     // Check the login:
13     list ($check, $data) = check_login($dbc,
       $_POST['email'], $_POST['pass']);
14
15     if ($check) { // OK!
16
17        // Set the session data:
18        session_start( );
19        $_SESSION['user_id'] = $data
          ['user_id'];
20        $_SESSION['first_name'] = $data
          ['first_name'];
21
22        // Store the HTTP_USER_AGENT:
23        $_SESSION['agent'] = md5($_SERVER
          ['HTTP_USER_AGENT']);
24
25        // Redirect:
26        redirect_user('loggedin.php'),
27
28     } else { // Unsuccessful!
29
30        // Assign $data to $errors for
          login_page.inc.php:
31        $errors = $data;
32
33     }
34
35     mysqli_close($dbc); // Close the
       database connection.
36
37  } // End of the main submit conditional.
38
39  // Create the page:
40  include ('includes/login_page.inc.php'),
41  ?>


One method of preventing hijacking is to store some sort of user identifier in the session, and then to repeatedly double-check this value. The HTTP_USER_AGENT—a combination of the browser and operating system being used—is a likely candidate for this purpose. This adds a layer of security in that one person could only hijack another user’s session if they are both running the exact same browser and operating system. As a demonstration of this, let’s modify the examples one last time.

To use sessions more securely

1. Open login.php (refer to Script 12.8) in your text editor or IDE.

2. After assigning the other session variables, also store the HTTP_USER_AGENT value (Script 12.12):

$_SESSION['agent'] = md5($_SERVER['HTTP_USER_AGENT']);

The HTTP_USER_AGENT is part of the $_SERVER array (you may recall using it way back in Chapter 1, “Introduction to PHP”). It will have a value like Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1...).

Instead of storing this value in the session as is, it’ll be run through the md5( ) function for added security. That function returns a 32-character hexadecimal string (called a hash) based upon a value. In theory, no two strings will have the same md5( ) result.

3. Save the file and place it in your Web directory.

4. Open loggedin.php (Script 12.9) in your text editor or IDE.

5. Change the !isset($_SESSION['user_id']) conditional to (Script 12.13):

if (!isset($_SESSION['agent']) OR ($_SESSION['agent'] != md5($_SERVER ['HTTP_USER_AGENT']) )) {

This conditional checks two things. First, it sees if the $_SESSION['agent'] variable is not set (this part is just as it was before, although agent is being used instead of user_id). The second part of the conditional checks if the md5( ) version of $_SERVER['HTTP_USER_AGENT'] does not equal the value stored in $_SESSION['agent']. If either of these conditions is true, the user will be redirected.

6. Save this file, place in your Web directory, and test in your Web browser by logging in.

Script 12.13. This loggedin.php script now confirms that the user accessing this page has the same HTTP_USER_AGENT as they did when they logged in.


1   <?php # Script 12.13 - loggedin.php #3
2   // The user is redirected here from
    login.php.
3
4   session_start( ); // Start the session.
5
6   // If no session value is present,
    redirect the user:
7   // Also validate the HTTP_USER_AGENT!
8   if (!isset($_SESSION['agent']) OR
    ($_SESSION['agent'] != md5($_SERVER
    ['HTTP_USER_AGENT']) )) {
9
10      // Need the functions:
11      require ('includes/login_functions.
        inc.php'),
12      redirect_user( );
13
14  }
15
16  // Set the page title and include the
    HTML header:
17  $page_title = 'Logged In!';
18  include ('includes/header.html'),
19
20  // Print a customized message:
21  echo "<h1>Logged In!</h1>
22  <p>You are now logged in, {$_SESSION
    ['first_name']}!</p>
23  <p><a href="logout.php">Logout</a></p>";
24
25  include ('includes/footer.html'),
26  ?>



Preventing Session Fixation

Another specific kind of session attack is known as session fixation. This approach is the opposite of session hijacking. Instead of malicious user Alice finding and using Bob’s session ID, she instead creates her own session ID (perhaps by logging in legitimately), and then gets Bob to access the site using that session. The hope is that Bob would then do something that would unknowingly benefit Alice.

You can help protect against these types of attacks by changing the session ID after a user logs in. The session_regenerate_id( ) does just that, providing a new session ID to refer to the current session data. You can use this function on sites for which security is paramount (like e-commerce or online banking) or in situations when it’d be particularly bad if certain users (i.e., administrators) had their sessions manipulated.



Tip

For critical uses of sessions, require the use of cookies and transmit them over a secure connection, if at all possible. You can even set PHP to only use cookies by setting session.use_only_cookies to 1.



Tip

By default, a server stores every session file for every site within the same temporary directory, meaning any site could theoretically read any other site’s session data. If you are using a server shared with other domains, changing the session.save_path from its default setting will be more secure. For example, it’d be better if you stored your site’s session data in a dedicated directory particular to your site.



Tip

The session data itself can also be stored in a database rather than a text file. This is a more secure, but more programming-intensive, option. I teach how to do this in my book PHP 5 Advanced: Visual QuickPro Guide.



Tip

The user’s IP address (the network address from which the user is connecting) is not a good unique identifier, for two reasons. First, a user’s IP address can, and normally does, change frequently (ISPs dynamically assign them for short periods of time). Second, many users accessing a site from the same network (like a home network or an office) could all have the same IP address.


Review and Pursue

If you have any problems with the review questions or the pursue prompts, turn to the book’s supporting forum (www.LarryUllman.com/forums/).

Review

• What code is used to redirect the user’s browser from one page to the next?

• What does the headers already sent error message mean?

• What value does $_SERVER['HTTP_HOST'] store? What value does $_SERVER['PHP_SELF'] store?

• What does the dirname( ) function do?

• What does the rtrim( ) function do? What arguments can it take?

• How do you write a function that returns multiple values? How do you call such a function?

• What arguments can the setcookie( ) function take?

• How do you reference values previously stored in a cookie?

• How do you delete an existing cookie?

• Are cookies available immediately after being sent (on the same page)? Why can you still refer to a cookie (on the same page) after it is deleted?

• What debugging steps can you take when you have problems with cookies?

• What does the basename( ) function do?

• How do you begin a session?

• How do you reference values previously stored in a session?

• Is session data available immediately after being assigned (on the same page)?

• How do you terminate a session?

• What debugging steps can you take when you have problems with sessions?

Pursue

• If you have not already done so, learn how to view cookie data in your Web browser. When developing sites that use cookies, enable the option so that the browser prompts you when cookies are received.

• Make the login form sticky.

• Add code to the handling of the $errors variable on the login page that uses a foreach loop if $errors is an array, or just prints the value of $errors otherwise.

• Modify the redirect_user( ) function so that it can be used to redirect the user to a page within another directory.

• Implement another cookie example, such as storing a user’s preference in the cookie, then base a look or feature of a page upon the stored value (when present).

• Change the code in logout.php (Script 12.11) so that it uses the session_name( ) function to dynamically set the name value of the session cookie being deleted.

• Implement another session example, if you’d like more practice with sessions (you’ll get more practice later in the book, too).

• Check out the PHP manual pages for any new function introduced in this chapter with which you’re not comfortable.

• Check out the PHP manual pages on cookies and sessions (two separate sections) to learn more. Also read some of the user-submitted comments for additional tips.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.88.7