Article
The PHP Anthology Volume 2, Chapter 1 - Access Control
Authentication in Action
Now that you've seen the internals of the Session and Auth classes, let's take a look at some code that makes use of them. First, here's the script that will act as the login form:
Example 1.12. 4.php
<?php
// If $_GET['from'] comes from the Auth class
if (isset($_GET['from'])) {
$target = $_GET['from'];
} else {
// Default URL: usually index.php
$target = '5.php';
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title> Login Form </title>
<meta http-equiv="Content-type"
content="text/html; charset=iso-8859-1" />
<style type="text/css">
body, a, td, input
{
font-family: verdana;
font-size: 11px;
}
h1
{
font-family: verdana;
font-size: 15px;
color: navy
}
</style>
</head>
<body>
<h1>Please log in</h1>
<form action="<?php echo $target; ?>" method="post">
<table>
<tr valign="top">
<td>Login Name:</td>
<td><input type="text" name="login" /></td>
</tr>
<tr valign="top">
<td>Password:</td>
<td><input type="password" name="password" /></td>
</tr>
<tr valign="top">
<td></td>
<td><input type="submit" value=" Login " /></td>
</tr>
</table>
</form>
</body>
</html>
At the beginning of the script, we check for the $_GET['from'] query string variable. If it exists, we use it as the action of the form (i.e. the page to which the form is submitted), so that a successful login will send the user to the requested page. Otherwise, a default target page is used (5.php in this example).
Later in this chapter, we'll reproduce this form using QuickForm, which may make an interesting comparison.
Next, let's look at the secure page:
Example 1.13. 5.php
<?php
// Include Magic Quotes stripping script
require_once 'MagicQuotes/strip_quotes.php';
// Include MySQL class
require_once 'Database/MySQL.php';
// Include Session class
require_once 'Session/Session.php';
// Include Auth class
require_once 'AccessControl/Auth.php';
$host = 'localhost'; // Hostname of MySQL server
$dbUser = 'harryf'; // Username for MySQL
$dbPass = 'secret'; // Password for user
$dbName = 'sitepoint'; // Database name
// Instantiate MySQL connection
$db = &new MySQL($host, $dbUser, $dbPass, $dbName);
// Instantiate the Auth class
$auth = &new Auth($db, '4.php', 'secret');
// For logging out
if (isset($_GET['action']) && $_GET['action'] == 'logout') {
$auth->logout();
}
?>
<!DOCTYPE html public "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title> Welcome </title>
<meta http-equiv="Content-type"
content="text/html; charset=iso-8859-1" />
<style type="text/css">
body, a, td, input
{
font-family: verdana;
font-size: 11px;
}
h1
{
font-family: verdana;
font-size: 15px;
color: navy
}
</style>
</head>
<body>
<h1>Welcome</h1>
<p>You are now logged in</p>
<?php
if (isset($_GET['action']) && $_GET['action'] == 'test') {
echo '<p>This is a test page. You are still logged in';
}
?>
<p><a href="<?php echo $_SERVER['PHP_SELF'];
?>?action=test">Test page</a></p>
<p><a href="<?php echo $_SERVER['PHP_SELF'];
?>?action=logout">Logout</a></p>
</body>
</html>
The only way the user can view this page is to have provided a correct user name and password. The moment that the Auth class is instantiated, it performs the security check. If valid user name and password values have been submitted via a form, they are stored by Auth in a session variable, allowing the visitor to continue surfing without having to log in again.
As promised, using the Auth class is very easy. To secure a page with it, all you need to do is place this at the start (Of course, you must also include the Auth.php file that contains the class definition with require_once):
// Instantiate the Auth class
$auth = &new Auth(&$db, $loginUrl);
As previously mentioned, $loginUrl is the URL to which the Auth class should redirect people who aren't already logged in.
Room for Improvement
The basic mechanics of the Auth class are solid, but it's missing the more involved elements that will be necessary to halt the efforts of any serious intruders.
It's a good idea to implement a mechanism to keep an eye on the number of failed login attempts made from a single client. If your application always responds immediately to any login attempt, it will be possible for a potential intruder to make large numbers of requests in a very short time, trying different user name and password combinations. The solution is to build a mechanism that counts the number of failed attempts using a session variable. Every time the number of failures is divisible by three (i.e. three incorrect passwords are entered), use PHP's sleep function to delay the next attempt by, for example, ten seconds. You may also decide that, after a certain threshold value (for example, fifteen failed attempts), you block all further access from that IP address for a given period (such as an hour). Of course, changing an IP address is much easier than changing a phone number, but you will at least stall would-be intruders, and may perhaps make their life difficult enough to persuade them to go elsewhere.
Another important ingredient for a good security system is an "event logging" mechanism that keeps track of suspicious access. In Chapter 4, Stats and Tracking, you'll find the basic mechanics of logging visitor information, including how to track IP addresses, while the observer pattern found in Chapter 7, Design Patterns could be used to "watch" the Auth class for the number of failed attempts.
You may want to tie the logging to some kind of alert mechanism that will warn you if someone attacks your site, giving you the chance to respond immediately. In critical environments, consider using an SMS gateway as your alerting system so that you'll receive notification even when you're not online.