Restricting access

Recall that DogToys has two parts to it:

DogToys

Figure 1. DogToys

One part of DogToys is for customers. That’s the blue area. It has pages for tasks that customers need to do. Like look at a list of products.

The other part of DogToys is for employees. That’s the green area. It has pages for tasks that employees need to do. Like add products and change prices.

But we don’t want just anyone messing with prices. This chapter shows you how to let different people access different pages, depending on what they are allowed to do.

You can try the secure version of the DogToys application. You have to look at the admin menu to see the changes (user names and passwords are hereboth are case-sensitive). As before, this version of DogToys won’t update product data.

You can download the files for the application. If you want to run the application on your own computer, you’ll need to change the database connection information.

The goal

Where are we?

We want to restrict who can see what pages on a site. Let’s make sure we understand what that means.

This lesson’s goals

Learn:

  • There two parts to restricting access: authentication and permissions.
  • Authentication is about knowing who the user is.
  • Permissions is about knowing what the user is allowed to do.
  • Create a database table with information about users, including their user names, passwords, and permissions.

Authentication and permissions

Here’s the situation:

Louise wants to change a price

Figure 1. Louise wants to change a price

Louise wants to change a product’s price. edit-product.php is the page that lets her do that.

We’ll add some code to DogToys, including edit-product.php. The new stuff needs to handle two things:

  • Authentication. Knowing which person is making the request. Is it Louise? Larry? Luna? Lenore?
  • Permissions. Is Louise allowed to change product data?

Authentication

We’ll give every person in the company a user name and a password. They’ll have to log in before they can use any of the administrative functions of DogToys (like edit prices, add products, and delete products).

The log in page will look like this:

Log in page

Figure 2. Log in page

The person types in his/her user name and password:

Louise logs in

Figure 3. Louise logs in

All of the admin pages are in a separate part of the site. They’re in the admin/ directory. There’s an admin menu, that shows all of the tasks authorized users are allowed to do.

Here is part of the admin menu, shown after the user logs in:

Admin menu

Figure 4. Admin menu

It shows the name of the logged in user. It has a link to log out. Click it, and the browser will jump back to the log in page.

Every admin page, like edit-product.php, will have some new PHP at the very beginning. It will make sure that someone is logged in:

if ( nobody is logged in )
  Jump to the log in page.
...

Figure 5. Log in check

If nobody is logged in, the browser will be sent to the log in page.

Permissions

So now we know who is logged in. But what is that user allowed to do?

Let’s add a table to the DogToys database.

users table

Figure 6. users table

We’ll actually use different field names later.

For each user, there’s a set of permissions. There’s a y if the user is allowed to do a task, like edit. If the user isn’t allowed, there’s an n.

Each page will check the permissions before running.

Let’s add to the security code in edit-product.php:

if ( nobody is logged in )
  Jump to log in page.
if ( edit permission is 'n' )
  Show "Permission denied" message.
  Stop.
... 

Figure 7. Log in and permissions check

What we need to do

We need to figure out how to:

  • Store data on user names, passwords, and permissions in the database.
  • Check this information on log in.
  • Check the user’s permissions when s/he visits a page.
  • Let the user log out.

You already know all the PHP you need, except for one thing: remembering who a user is once s/he has logged in. We’ll look at that in the next lesson.

Summary

  • There two parts to restricting access: authentication and permissions.
  • Authentication is about knowing who the user is.
  • Permissions is about knowing what the user is allowed to do.
  • Create a database table with information about users, including their user names, passwords, and permissions.

What now?

Let’s talk about PHP sessions. DogToys will use sessions to remember who has logged in.

PHP sessions

Where are we?

This chapter is about restricting access to pages on a Web site. We just talked about the goal: a workflow with log in, actions, and log out.

This page talks about PHP sessions. We’ll use use to create the workflow we want.

This lesson’s goals

Learn that:

  • Session memory is separate from any one page.
  • Pages can store data in session memory, and retrieve it.
  • Sessions can be destroyed by PHP pages. Sessions expire after a time of inactivity.

The goal

Louise works for DogToys, keeping product information up-to-date. Here’s one of Louise’s work sessions.

Louise's work session

Figure 1. Louise’s work session

Louise logs in. Then she does her work: adding products, editing products, whatever she needs to do. At the end of the session, she logs out.

Once she has logged in, all of the pages – edit-product.php, add-product.php, and so on – know who she is, and what permissions she has. She doesn’t have to log in again for each change.

This is the workflow we want. But…

The problem

Each PHP page is a program running on the server. When the program starts running, it asks the server’s operating system for some memory, where it can store variables.

Page memory

Figure 2. Page memory

Each time the code makes a new variable, a place is created for the variable in the page’s memory.

Here’s an important thing: when the page is finished, the page’s memory is erased. The server’s operating system grabs the memory back. So there’s nothing left of the variables. They’re all gone.

Every page has its own memory space:

Each page has some memory

Figure 3. Each page has some memory

All of the page memories are independent. Nothing is shared.

Remember we want to have a log in page, log-in.php. How does this page let the other pages know that Louise is allowed to use the system? log-in.php has to leave something behind to say, “Louise is OK.” But how?

That’s where PHP sessions come in. Sessions do a number of things, but the most important is that they create some memory on the server that exists separately from any one page:

Session memory

Figure 4. Session memory

log-in.php can put variables into the session memory. When the server erases log-in.php’s memory, the session memory will still be there.

Other pages can access the session memory. add-product.php can look in it, and see what log-in.php left behind.

This is how we can make authentication work. When a user logs in, we can put some data into the session memory. Other pages can check the session memory, to see what the user is allowed to do.

Each person who connects to DogToys gets their own session memory:

Separate sessions

Figure 5. Separate sessions

Actually, the server doesn’t create a separate session for each person, but for each browser that connects. Each browser will be running on a machine with a different IP address:

Sessions for IP addresses

Figure 6. Sessions for IP addresses

There’s a little more to it, but it’s close enough.

But we’ll continue to talk about user sessions, not IP sessions. It’s simpler.

Suppose each user in Figure 5 – Louise, Jim, Clara, and Joe – connects to log-in.php. When log-in.php runs, it uses separate session data for each user:

Separate sessions

Figure 7. log-in.php uses separate sessions

This means that each person’s log in data is kept separate.

Different users will run the same page, like log-in.php. But log-in.php will see different data for each user, because it is pulling data from each user’s own session memory.

Here’s an example. It isn’t accurate about how session data is stored; we’ll see that in a minute.

log-in.php uses different values for each user

Figure 8. log-in.php uses different values for each user

log-in.php outputs $p. What will it show? Because the data comes from session memory, each user would see something different.

  • Louise sees 17.
  • Jim sees 23.
  • Clara sees 29.
  • Joe sees 31.

So the same code – print $p; – could show something different when it is run by different users, if it is using data from session memory.

Let’s look at the PHP statements that mess with sessions. We’ll only look at the core statements.

PHP’s session statements

Starting a session

You need to start the session mechanism before you can use it.

session_start();

This statement has to appear before the page outputs anything, or sessions won’t work.

Storing session data

Once a session has been started, you can store data into it using the $_SESSION array. For example:

$_SESSION['user name'] = 'jimmy';

The string between the quotes names the session data. Other examples:

$_SESSION['favorite number'] = 96;
$_SESSION['favorite color'] = 'green';
$_SESSION['database server'] = 'localhost';

Retrieving session data

You can retrieve session data as well. Again, you have to start the session first.

$user name = $_SESSION['user name'];
$num = $_SESSION['favorite number'];
$best_color = $_SESSION['favorite color'];
$db_server_host = $_SESSION['database server'];

Destroying a session

Destroy a session like this:

session_start();
$_SESSION = array();
session_destroy();

The second line erases the session data. The third one wipes out the session itself.

An example

Let’s look at a sample application that uses session. The user enters his/her name:

User enters name

Figure 9. User enters name

The name is stored in the session.

The user then sees a menu, like this:

Menu

Figure 10. Menu

The user’s name is inserted into captions of photos. Here’s what clicking on the Renata link shows:

Renata

Figure 11. Renata

You can try the application, and download its files.

Here are the pages in the application:

Pages in the application

Figure 12. Pages in the application

Here are the pieces:

  • ask-user-name.html shows the form that gets the user’s name.
  • remember-name.php stores the user’s name in the session.
  • menu.php shows the application’s menu.
  • cc.php, kieran.php, and renata.php show photos with the user’s name in the captions.
  • new-name.php erases the user’s name from the session, and jumps back to the user name form.

Let’s look at the code.

Getting the user’s name

ask-user-name.html shows the form:

User enters name

Figure 9 (again). User enters name

The form passes the data to remember-name.php, which stores the data into the session.

Here’s the code for remember-name.php:

<?php
//Get the name the user typed.
$user_name = $_POST['user_name'];
//Validate.
if ( $user_name == '' ) {
  header('location:ask-user-name.html');
  exit();
}
//Start session.
session_start();
//Store data in session.
$_SESSION['user name'] = $user_name;
//Go to the main menu.
header('location:menu.php');
?>

Figure 13. remember-name.php

remember-name.php doesn’t output anything for the user to see. It puts data into the session, for other pages to use.

Line 3 gets the value the user typed into the form. Lines 5 to 8 check whether the value is empty. If it is, the browser is told to jump back to ask-user-name.html.

Now to store the name into the session. Line 10 starts the session:

session_start();

PHP pages need to do this before they can use sessions.

Line 12 stores the user name into the session:

$_SESSION['user name'] = $user_name;

Line 14 jumps to the menu:

header('location:menu.php');

The menu

Here’s the code for menu.php.

<?php
//Do we know the user's name?
session_start();
$user_name = $_SESSION['user name'];
if ( $user_name == '' ) {
  header('location:ask-user-name.html');
  exit();
}
?><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title>Menu | Sessions Sample App</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>
    <h1>Menu</h1>
    <p>Name: <?php print $user_name; ?></p>
    <ul>
      <li><a href="cc.php">CC photo</a></li>
      <li><a href="kieran.php">Kieran photo</a></li>
      <li><a href="renata.php">Renata photo</a></li>
      <li><a href="new-name.php">Use a different name</a></li>
    </ul>
  </body>
</html>

Figure 14. menu.php

Line 3 starts the session.

Line 4 gets some data from the session:

$user_name = $_SESSION['user name'];

Lines 5 to 8 checks that there is a name. If not, the browser is sent to ask-user-name.html.

Renata
Renata

How could $_SESSION['user name'] be empty? It was just set in remember-name.php, wasn’t it?

Kieran
Kieran

You’re right, remember-name.php sets
$_SESSION['user name'], and then it jumps to menu.php.

But that’s only one way the user can get to menu.php. There are other ways. For example, the user can type the page’s URL directly into the browser:

User types menu.php into the browser

Figure 15. User types menu.php into the browser

This skips remember-name.php, so the session data isn’t set.

It’s best not to assume that the session data is there.

The menu shows the user name:

Menu

Figure 10 (again). Menu

Here’s line 17 from menu.php:

<p>Name: <?php print $user_name; ?></p>

Showing a photo

Here’s renata.php, that shows the user’s name in a photo caption:

<?php
//Do we know the user's name?
session_start();
$user_name = $_SESSION['user name'];
if ( $user_name == '' ) {
  header('location:ask-user-name.html');
  exit();
}
?><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Renata Photo | Sessions Sample App</title>
  </head>
  <body>
    <div id="container">
      <p><img src="renata.jpg" alt="CC"></p>
      <h1 class="loves">Renata loves <?php print $user_name; ?></h1>
    </div>
    <p><a href="menu.php">< Menu</a></p>
  </body>
</html>

Figure 16. renata.php

Line 3 starts the session. Line 4 gets the user’s name from the session:

$user_name = $_SESSION['user name'];

Lines 5 to 8 check whether there is a user name. If not, jump to ask-user-name.html.

Line 18 puts the user name into the photo caption:

<h1 class="loves">Renata loves <?php print $user_name; ?></h1>

Changing the user name

What if the user wants to change the name that shows in the captions? There’s a link for that in the main menu.

Menu

Figure 10 (again). Menu

The last link in the menu sends the browser to new-name.php. Here is the code:

<?php
session_start();
//Erase the session variables.
$_SESSION = array();
//Kill the session.
session_destroy();
header('location:ask-user-name.html');
?>

Figure 17. new-name.php

new-name.php erases the session data (line 4) and kills the session itself (line 6). Then it sends to browser back to the user name form.

Again, new-name.php doesn’t output anything itself. It does something to the session, and jumps to another page.

Session lifetime

You might be asking yourself:

Self, do sessions last forever?

Sessions are destroyed when:

  • A PHP program runs session_destroy();
  • The browser is closed.
  • The session expires.

Sessions expire if the browser does not access a page in the application within a certain amount of time. You can set the amount of time sessions last. The default is 20 minutes.

Exercise: Event list

The Dog Center is a meeting center that holds lectures, demonstrations, workshops, and other events. About dogs, of course.

The Center’s management want to put information kiosks at the doors. They’ll be PCs with touch screens, running a Web browser. People can use the kiosks to learn about the events at the Center that day.

Here’s what the kiosk’s screen will look like for a typical day:

Event list

Figure 1. Event list

Clicking on a link shows an event:

Event

Figure 2. Event

There’s a joke at the bottom of every page. When a kiosk is started for the day, it asks the user for a joke:

Asking for a joke

Figure 3. Asking for a joke

The joke is stored in the session. It’s then shown on every page.

Management wants to be able to change the joke if need be. Add a link to the menu to do that. But the link should be hidden on the lower right of the page. The mouse cursor should change when passing over the link, but the link itself should not show:

Secret link

Figure 4. Secret link

When the link is clicked, the browser asks for a joke again, as in Figure 3.

You can try my solution. You can also download the files, but do it yourself first!

Hint: what happens when color and background-color are the same?

Upload your solution to your hosting account. Put the URL below.

(Log in to enter your solution to this exercise.)

Can't find the 'comment' module! Was it selected?

Summary

  • Session memory is separate from any one page.
  • Pages can store data in session memory, and retrieve it.
  • Sessions can be destroyed by PHP pages. Sessions expire after a time of inactivity.

What now?

This chapter is about restricting access to pages on a Web site. You just saw how sessions let PHP pages share information. Now let’s talk about storing data about users and their permissions.

Storing user data

Where are we?

You’ve seen the workflow we want in DogToys. You’ve seen how sessions work.

We’ll need some way to remember user names and passwords, and the permissions that each user has. Let’s adding user data to the DogToys database.

This lesson’s goals

Learn:

  • Create a users table in the database. It will have user names, passwords, and permission flags.
  • Good passwords have lowercase letters, uppercase letters, digits, and special characters. They don’t correspond to a dictionary word.

The users table

Let’s add a table called users to the DogToys database. It will have the data we need for authentication and permissions. Here are the fields:

users table

Figure 1. users table

user_id is the primary key, an unsigned integer. auto_increment means that MySQL will automatically supply values when new records are added to the users table.

user_name is, er, the user name. It can be up to 20 characters long.

password is the user’s password. Like user_name, it can be up to 20 characters long.

We’re storing the password in clear text. You wouldn’t do that in a secure application.

permission_add is a one-character field that is either y or n. If it’s y, the user has permission to add products.

permission_edit is a one-character field as well. If it’s y, the user can change data about existing products.

permission_delete controls whether users are allowed to delete product records.

Sample data

Here’s some data. I broke the image into pieces so it would fit on this page.

Users data

Figure 2. Users data

You can see that Kieran and Louise are allowed to do anything to the data. But Renata can only edit data. She can’t add products or delete them.

Look at the passwords. Renata’s password is terrible. If a hacker learned Renata’s user name, one of the first things s/he would try is using the same thing for the password.

Loiuse’s password isn’t very good either. The characters are all the same.

CC’s password is bad as well. It’s a single word. It’s vulnerable to “dictionary” attacks, where a program tries to log in using English words as passwords. Eventually, it would hit “sheltie.” The word is in dictionary.com.

Kieran’s password is the only good one. It uses four different character types:

  • Lowercase letters
  • Uppercase letters
  • Digits
  • Special characters

It’s not vulnerable to dictionary attacks, and is hard to guess.

Maintaining user data

We need to be able to:

  • Add new user records
  • Let users change their passwords
  • Delete records when people leave the company
  • Change permissions when people change jobs

In a real Web application, we’d write Web pages for these tasks. We’d assign someone as an administrator. We’d add a permissions field that would let an administrator access those the pages that change user data.

To keep things simple, we don’t do that in this chapter. It’s not core.

You can change the user data with phpMyAdmin.

Exercise: DogJokes users table

Add a users table to the DogJokes database you created in the previous chapter. Use the same fields I used for the DogToys users table. Add some records.

Can't find the 'comment' module! Was it selected?

Summary

  • Create a users table in the database. It will have user names, passwords, and permission flags.
  • Good passwords have lowercase letters, uppercase letters, digits, and special characters. They don’t correspond to a dictionary word.

What now?

Now let’s see how you use the table in the log in process.

Logging in

Where are we?

This chapter is about restricting access to pages on a Web site.

You know how sessions let PHP pages share information. You’ve seen how we can store data about users and their permissions in a database table.

Now let’s create the log in system that uses that data.

This lesson’s goals

You will learn that:

  • The log in form gets a user name and password from the user.
  • It sends the data to a page that checks whether the user name and password is in the database table users.
  • If the user name and password are found, permission information is stored in the session.

Overview

We want the user to see a log in form, like this:

Louise logs in

Figure 1. Louise logs in

If the user name and password match a record in the database, then the user would see the admin menu.

Admin menu

Figure 2. Admin menu

Here are the pages we’ll create:

Pages involved in log in

Figure 3. Pages involved in log in

log-in.php shows the form. The user completes it, and clicks the submit button. log-in.php passes the data to check-log-in.php. check-log-in.php checks the database for a matching record in the users table. If it finds one, it stores some data in the session, and sends the browser to the admin menu page, admin/index.php. If there is no matching record, the browser is sent back to the log in form.

Let’s look at the code.

log-in.php

Here’s the log in form again:

Louise logs in

Figure 1 (again). Louise logs in

The form does some error checking. There are client-side checks for empty fields:

Empty field errors

Figure 4. Empty field errors

But what if the user fills in both fields with an unknown user name and password? This check cannot be done on the client-side. A server-side program has to look up the user name and password before finding that the user name/password combination is unknown.

Here’s the error reporting for a unknown user name/password combination:

Unknown user name/password error

Figure 5. Unknown user name/password error

So the page needs to:

  • Check for missing data on the client side.
  • Report errors found on the server side.

The page will also have to:

  • Skip the log in process if the user is already logged in.
  • Implement the template system.

That’s quite a lot of stuff for log-in.php to do!

Here’s the complete code. We’ll go over all the pieces.

<?php
//Show the admin menu
//Input: order(optional). Sort order for article list. GET.

//Path from this page to the site root.
$path_to_root = '..';
//Already logged in?
session_start();
if ( $_SESSION['logged in'] == 'y' ) {
  //Yes - jump to admin menu.
  header("location:$path_to_root/admin/index.php");
  exit();
}
//Title of this page.
$page_title = 'DogToys Log In';
require $path_to_root . '/library/start_page.inc';
require $path_to_root . '/library/header.inc';
require $path_to_root . '/library/left_nav.inc';
?>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script type="text/javascript" src="<?php print $path_to_root;?>/library/show-error-messages.js"></script>
<script type="text/javascript">
  $(document).ready(function() {
    //Initialization.
    $("#global_error_message_container").hide();
    $("#user_name").focus();
    <?php
      if ( $_GET['err'] == 'auth' ) {
        print("show_global_error_message('Sorry, user name or password not found.');");
      }
    ?>
    //Form submit event.
    $("#log_in_form").submit(function() {
      //Set flag showing everything is OK.
      var data_ok = true;

      //Check the user name field.
      var user_name = $("#user_name").val();
      if ( user_name == '' ) {
        data_ok = false;
        show_field_error_message('Sorry, you must enter a user name.', 'user_name');
        show_global_error_message("Sorry, I can't log you in.");
      }
      else {
        hide_error_message('user_name');
      }

      //Check the password field.
      var user_password = $("#user_password").val();
      if ( user_password == '' ) {
        data_ok = false;
        show_field_error_message('Sorry, you must enter a password.', 'user_password');
        show_global_error_message("Sorry, I can't log you in.");
      }
      else {
        hide_error_message('user_password');
      }

      return data_ok;
    });
  });
</script>

<div id="center_region">
  <h1><?php print $page_title; ?></h1>
  <p id="global_error_message_container" class="message_container">
    <img src="<?php print $path_to_root; ?>/library/error.png" alt="Error">
    <span id="global_error_message"/>
  </p>
  <form id="log_in_form" method="post" action="check-log-in.php">
    <p>
      User name<br>
      <input type="text" id="user_name" name="user_name" size="20"><br>
      <span id="user_name_message_container" class="message_container">
        <img src="<?php print $path_to_root; ?>/library/error.png" alt="Error">
        <span id="user_name_message"/>
      </span>
    </p>
    <p>
      Password<br>
      <input type="password" id="user_password" name="user_password" size="20"><br>
      <span id="user_password_message_container" class="message_container">
        <img src="<?php print $path_to_root; ?>/library/error.png" alt="Error">
        <span id="user_password_message"/>
      </span>
    </p>
    <p>
      <button type="submit">Log in</button>
    </p>
  </form>
</div>
<?php
require $path_to_root . '/library/footer.inc';
require $path_to_root . '/library/end_page.inc';
?>

Figure 6. log-in.php

Line 8 starts the session.

Lines 9 to 13 check whether the user has already logged in. This…

$_SESSION['logged in']

...looks for the session variable logged in. If it has the value y, the user is already logged in. The browser is sent to the admin menu immediately, skipping the log in process.

Lines 14 to 18 are part of the templating system.

Lines 20 to 62 mostly implement client-side error checking. But lines 27 to 31 are new. They show errors from the server.

Here are the pages again:

Pages involved in log in

Figure 3 (again). Pages involved in log in

check-log-in.php is the page that finds whether the user name and password are valid. If they aren’t, is sends something to log-in.php, to tell it to show an error message. But what should it send, and how?

Here’s one way to do it. When check-log-in.php finds an error, it tells the browser to jump to log-in.php. It attaches something to the URL. Here is some code from check-log-in.php :

header("location:$path_to_root/admin/log-in.php?err=auth");

See the err=auth bit? This tells log-in.php to show an authentication error. Here’s how the URL looks in a browser’s address bar:

Error flag in URL

Figure 7. Error flag in URL

When log-in.php receives this, it can show the error message.

We already have some JavaScript code in log-in.php for showing errors. It uses colors, shows an icon, and other Good Stuff. Let’s use that existing JavaScript code to report the server error.

Here’s some code from the JavaScript part of log-in.php.

$(document).ready(function() {
  //Initialization.
  $("#global_error_message_container").hide();
  $("#user_name").focus();
  <?php
    if ( $_GET['err'] == 'auth' ) {
      print("show_global_error_message('Sorry, user name or password not found.');");
    }
  ?>

Figure 8. Part of log-in.php

The JavaScript function show_global_error_message() shows an error message at the top of the page. Line 29 outputs JavaScript that would call that function:

show_global_error_message('Sorry, user name or password not found.');

The PHP in line 28 adds this JavaScript if log-in.php has err passed to it, with the value auth. Recall that GET attaches stuff to the URL.

So, if the server program check-log-in.php can’t find the user name and password in the users database table, it jumps back to log-in.php, with err set to auth.

If log-in.php gets err set to auth, it calls some JavaScript to show an error message. This is PHP writing JavaScript. Again.

The rest of log-in.php should be familiar. But there’s a new thing in line 81:

<input type="password" id="user_password" name="user_password" size="20">

An <input> field with a type of password is almost the same as one with a type of text. The only difference is that the browser doesn’t show what the user types into a password field:

Password field

Figure 9. Password field

check-log-in.php

Here are the pages again:

Pages involved in log in

Figure 3 (again). Pages involved in log in

check-log-in.php gets a user name and password from log-in.php. It looks them up in the database. If they’re found, it sets some session data, and jumps to the admin menu. If the user name and password are not found, check-log-in.php jumps back to log-in.php.

Here is the code:

<?php
//Path from this page to the site root.
$path_to_root = '..';
//Get the values the user typed.
$user_name = $_POST['user_name'];
$user_password = $_POST['user_password'];
//Validate
if ( $user_name == '' || $user_password == '') {
  header("location:$path_to_root/admin/log-in.php");
  exit();
}
//Connect to DB.
require $path_to_root . '/library/db-connect.php';
$db = new mysqli($db_host, $db_user_name, $db_password, $db_name);
if ( mysqli_connect_error() ) {
  print '<p>Error! Could not connect to the database. ';
  print 'Error message: '.mysqli_connect_error().'</p>';
  exit();
}
//Fetch article data.
$query = "select permission_add, permission_edit, permission_delete
    from users
    where binary user_name = '$user_name'
        and binary user_password = '$user_password'";
$record_set = $db->query($query);
if ( $record_set->num_rows == 1 ) {
  session_start();
  $_SESSION['logged in'] = 'y';
  $_SESSION['user name'] = $user_name;
  //Store permissions in session.
  $row = $record_set->fetch_assoc();
  $_SESSION['permission add'] = $row['permission_add'];
  $_SESSION['permission edit'] = $row['permission_edit'];
  $_SESSION['permission delete'] = $row['permission_delete'];
  //Jump to admin menu.
  header("location:$path_to_root/admin/index.php");
  exit();
}
//Authentication problem.
header("location:$path_to_root/admin/log-in.php?err=auth");
exit();
?>

Figure 10. check-log-in.php

Lines 5 and 6 get the data sent to the page:

$user_name = $_POST['user_name'];
$user_password = $_POST['user_password'];

Lines 8 to 11 make sure that the data is there:

if ( $user_name '' || $user_password '') {
   header("location:$path_to_root/admin/log-in.php");
   exit();
}

If it isn’t, the browser is sent back to log-in.php. This should never happen, of course, because of the client-side validation in log-in.php. These lines are an extra security check.

Now we need to check the database, and see if the user name and password are valid. Lines 13 to 19 connect to the database.

Lines 21 to 24 create the SQL query that will look up the user name and password:

select permission_add, permission_edit, permission_delete
from users
where binary user_name = '$user_name'
and binary user_password = '$user_password'

Two things to notice. The first is the and. The select will only return records where both conditions are true.

The second thing is the binary keyword. MySQL tests are usually not case-sensitive. So louise, Louise, and lOUise are all the same.

But we don’t want that here. Applications are more secure if user names and passwords as case-sensitive, so louise and Louise are not the same. Adding the binary keyword makes the tests case-sensitive.

Line 25 is:

$record_set = $db->query($query);

It tells MySQL to run the query, returning a record set.

If the user name and password are valid, the record set should return exactly one record. That gets tested in the next piece of code.

if ( $record_set->num_rows == 1 ) {
  session_start();
  $_SESSION['logged in'] = 'y';
  $_SESSION['user name'] = $user_name;
  //Store permissions in session.
  $row = $record_set->fetch_assoc();
  $_SESSION['permission add'] = $row['permission_add'];
  $_SESSION['permission edit'] = $row['permission_edit'];
  $_SESSION['permission delete'] = $row['permission_delete'];
  //Jump to admin menu.
  header("location:$path_to_root/admin/index.php");
  exit();
}

Figure 11. Part of check-log-in.php

The code…

$record_set->num_rows

...returns the number of rows in the record set. If one record was returned, the user name and password is valid. In that case, we want to store stuff in the session.

This line…

$_SESSION['logged in'] = 'y';

...sets logged in to y. This lets other pages easily test whether there is a logged in user. We’ll see that later.

This line…

$_SESSION['user name'] = $user_name;

...stores the user name passed into check-log-in.php in the session.

Line 31 fetches the row in the record set. Remember that we already know there is only one row.

Lines 32 to 34 get the values of the three permissions fields. They are stored in the session.

Line 36 jumps to the admin menu.

Here’s what happens if the user name/password combination is not found:

if ( $record_set->num_rows == 1 ) {
  ...
  exit();
}
//Authentication problem.
header("location:$path_to_root/admin/log-in.php?err=auth");

Figure 12. Another part of check-log-in.php

The browser is sent back to log-in.php, with a flag showing that log-in.php should show an error message.

Summary

  • The log in form gets a user name and password from the user.
  • It sends the data to a page that checks whether the user name and password is in the database table users.
  • If the user name and password are found, permission information is stored in the session.

What now?

Now we have a log in system that leaves data in the session. Let’s see how other pages use that data for security.

Checking permissions

Where are we?

We want to restrict access to the administration section of a Web site. We have some log in code that checks for a valid user name and password. If it finds one, it puts data into the session.

Let’s see how we can use that session data to restrict access to pages.

This lesson’s goals

Learn:

  • Every admin page checks the log in flag in the session. You can put the code in a separate file, and use the require statement to insert it.
  • Admin pages can check permission data in the session.
  • Use permission data from the session to change the admin interface. Don’t show users actions they’re not allowed to do.

Using session data

Here’s Louise’s workflow again:

Louise's workflow

Figure 1. Louise’s workflow

The figure omits pages Louise doesn’t see, like check-log-in.php.

We saw the log in process in the previous lesson. It stores session data about the user, like this:

$_SESSION['logged in'] = 'y';
$_SESSION['user name'] = $user_name;
...
$_SESSION['permission add'] = $row['permission_add']; $_SESSION['permission edit'] = $row['permission_edit'];
$_SESSION['permission delete'] = $row['permission_delete'];

Figure 2. Storing log in data into the session

Pages like index.php, add-product.php, and edit-product.php will use the session data to check:

  • Whether the user is logged in.
  • What permissions the user has.

Let’s see how that works.

Is the user logged in?

During log in, check-log-in.php does this if the user gives a valid user name and password:

$_SESSION['logged in'] = 'y';

Other pages, like add-product.php, can use this to check whether the user is logged in. Here’s some code.

<?php
//Start session mechanism.
session_start();
//User logged in?
if ( $_SESSION['logged in'] != 'y' ) {
  //No - jump to log in page.
  header("location:$path_to_root/admin/log-in.php");
  exit();
}
?>

Figure 3. Log in check

Line 3 starts the session. Line 5 checks the variable $_SESSION['logged in']. Recall that != means “not equal to.” So if $_SESSION['logged in'] has anything other than y, the browser is sent back to the log in page.

W00f!

This check needs to be done on every page of the site’s admin section. We could copy-and-paste the code everywhere, but there’s a better way. We’ll put the code in Figure 3 in the file library/restrict.php. Then we’ll use the require statement to insert it into all the admin pages. Here’s how add-product.php starts:

<?php
//Get data from user for a new product.
//Input:
//  None.

//Path from this page to the site root.
$path_to_root = '..';
//Security check
require $path_to_root . '/library/restrict.php';

Figure 4. Code from add-product.php

Line 9 loads the file. That’s all add-product.php needs to do.

Checking permissions

We can make an admin page check whether a user is logged in. But what about checking individual permissions?

There are two parts to this:

  • Stopping the user from doing things s/he is not allowed to do.
  • Changing the interface, so that links to things the user is not allowed to do don’t even show up.

Stopping the user

Remember this, from check-log-in.php:

$_SESSION['permission add'] = $row['permission_add'];

Admin pages can check $_SESSION['permission add'], to see whether the user is allowed to add products.

Here’s code from add-products.php:

<?php
...
//Security check
require $path_to_root . '/library/restrict.php';
if ( $_SESSION['permission add'] != 'y' ) {
  //Exit if user doesn't have add permission.
  header("location:$path_to_root/admin/index.php");
  exit();
}

Figure 5. More code from add-product.php

Line 4 checks whether the user is logged in, as we saw above.

Line 5 checks whether the user has permission to add products. If not, the browser is sent to the admin menu.

Here’s code from delete-product.php.

<?php
...
//Security check
require $path_to_root . '/library/restrict.php';
if ( $_SESSION['permission delete'] != 'y' ) {
  //Exit if user doesn't have delete permission.
  header("location:$path_to_root/admin/index.php");
  exit();
}

Figure 6. Code from delete-product.php

It’s the same, except that it checks a different permission.

Changing the interface

The admin interface should change, depending on what permissions the user has. Here’s Kieran’s admin menu:

Kieran's admin menu

Figure 7. Kieran’s admin menu

Because Kieran has permission to add, edit, and delete products, links for all of those tasks appear in the menu.

Here’s Renata’s admin menu:

Renata's admin menu

Figure 8. Renata’s admin menu

Renata does not have permission to add or delete products. Only to edit products. So only edit links show up in the interface.

How to make this happen? Let’s start with the “Add a new product” link. We want that to appear only for users who have permission to add new products. Here’s code from the admin menu:

<p>User: <?php print $_SESSION['user name']; ?></p>
<p>What do you want to do?</p>
<ul>
  <li><a href="log-out.php">Log out</a></li>
  <?php
  if ( $_SESSION['permission add'] == 'y' ) {
    ?>
      <li><a href="add-product.php">Add a new product</a></li>
    <?php
  }
  ?>

Figure 9. Code from admin/index.php

Line 1 shows the user name. You can see it on the page:

Kieran's admin menu

Figure 7 (again). Kieran’s admin menu

Line 4 shows a link to the log out page. We’ll look at that in the next lesson.

The next lines are:

if ( $_SESSION['permission add'] == 'y' ) {
   ?>
   <li><a href="add-product.php">Add a new product</a></li>
   <?php
}

The link to add-product.php is output to the page only if the user has permission to add pages.

What about the edit and delete links?

Kieran's admin menu

Figure 7 (again). Kieran’s admin menu

They’re done the same way.

print "
...
<td>
";
if ( $_SESSION['permission edit'] == 'y' ) {
  print "<a href='edit-product.php?id=$product_id'>Edit</a><br>";
}
if ( $_SESSION['permission delete'] == 'y' ) {
  print "<a href='confirm-delete-product.php?id=$product_id'>Delete</a>";
}
print "
</td>
...";

Figure 10. More code from admin/index.php

The code that outputs the links is wrapped in if statements that check permissions.

Summary

  • Every admin page checks the log in flag in the session. You can put the code in a separate file, and use the require statement to insert it.
  • Admin pages can check permission data in the session.
  • Use permission data from the session to change the admin interface. Don’t show users actions they’re not allowed to do.

What now?

You know how to make log in pages store security data in the session. You know how to use that data to restrict what users can do.

Now let’s see how users can log out.

Logging out

Where are we?

This chapter is about restricting access to the admin section of a Web site.

You know how to add a log in page, store log in data in sessions, and check it in every admin page.

The only thing left to do is let the user log out.

This lesson’s goals

Learn:

  • How to add a log out link on the admin menu.
  • The log out page gets rid of the session data.

The admin menu (admin/index.php) has a log out link. The user clicks it to log out:

Log out link

Figure 1. Log out link

Here’s the HTML that makes the link:

<a href="log-out.php">Log out</a>

Just a simple link. But that does the log out page do?

Logging out

The code we wrote to restrict access uses session data. The log out code gets rid of it.

Here’s the code for log-out.php.

<?php
//Log out
//Path from this page to the site root.
$path_to_root = '..';
session_start();
//Kill all the the session variables.
$_SESSION = array();
//Kill the session itself.
session_destroy();
//Back to the log in page.
header("location:$path_to_root/admin/log-in.php");
exit();
?>

Figure 2. log-out.php

Line 7 erases the session data, including the log in flag and all permissions information. Line 9 destroys the session itself. Line 11 jumps back to the log in page.

That’s it!

W00f!

Renata
Renata

The erases all of the session data, right?

Kieran
Kieran

Yes.

Renata
Renata

Is there session data you might not want to erase?

Kieran
Kieran

Ooo, good question!

You can use the session to store data about anything. Like the winner of the Tokyo dog show.

In the code we’ve been looking at, we only used the session to store information about log in and permission. So erasing it all makes sense.

But if you’re using the session to store other information as well, you might want to just erase the log in and permission stuff, like this:

$_SESSION['logged in'] = '';
$_SESSION['permission add'] = '';
$_SESSION['permission edit'] = '';
$_SESSION['permission delete'] = '';

Summary

  • There’s a log out link on the admin menu.
  • The log out page gets rid of the session data.

What now?

Now for some exercises.

Exercises: Restricting access