| only for RuBoard - do not distribute or recompile |
There are four main elements to the user authentication module: user registration, login and logout, changing passwords, and resetting passwords. We will look at each of these in turn.
To register a user, we need to get his details via a form and enter him in the database.
When a user clicks on the "Not a member?" link on the login.php page, they will be taken to a registration form produced by register_form.php. This script is shown in Listing 24.5.
<?
require_once("bookmark_fns.php");
do_html_header("User Registration");
display_registration_form();
do_html_footer();
?>
Again, you can see that this page is fairly simple and just calls functions from the output library in output_fns.php. The output of this script is shown in Figure 24.4.

The gray form on this page is output by the function display_registration_form(), contained in output_fns.php. When the user clicks on the Register button, he will be taken to the script register_new.php. This script is shown in Listing 24.6.
<?
// include function files for this application
require_once("bookmark_fns.php");
// start session which may be needed later
// start it now because it must go before headers
session_start();
// check forms filled in
if (!filled_out($HTTP_POST_VARS))
{
do_html_header("Problem:");
echo "You have not filled the form out correctly - please go back"
." and try again.";
do_html_footer();
exit;
}
// email address not valid
if (!valid_email($email))
{
do_html_header("Problem:");
echo "That is not a valid email address. Please go back "
." and try again.";
do_html_footer();
exit;
}
// passwords not the same
if ($passwd != $passwd2)
{
do_html_heading("Problem:");
echo "The passwords you entered do not match - please go back"
." and try again.";
do_html_footer();
exit;
}
// check password length is ok
// ok if username truncates, but passwords will get
// munged if they are too long.
if (strlen($passwd)<6 || strlen($passwd) >16)
{
do_html_header("Problem:");
echo "Your password must be between 6 and 16 characters."
."Please go back and try again.";
do_html_footer();
exit;
}
// attempt to register
$reg_result = register($username, $email, $passwd);
if ($reg_result == "true")
{
// register session variable
$valid_user = $username;
session_register("valid_user");
// provide link to members page
do_html_header("Registration successful");
echo "Your registration was successful. Go to the members page "
."to start setting up your bookmarks!";
do_HTML_URL("member.php", "Go to members page");
}
else
{
// otherwise provide link back, tell them to try again
do_html_header("Problem:");
echo $reg_result;
do_html_footer();
exit;
}
// end page
do_html_footer();
?>
This is the first script with any complexity to it that we have looked at in this application.
The script begins by including the application's function files and starting a session. (When the user is registered, we will create his username as a session variable as we did in Chapter 20, "Using Session Control in PHP." )
Next, we validate the input data from the user. There are a number of conditions we must test for. They are
Check that the form is filled out. We test this with a call to the function filled_out() as follows:
if (!filled_out($HTTP_POST_VARS))
This function is one we have written ourselves. It is in the function library in the file data_valid_fns.php. We'll look at this function in a minute.
Check that the email address supplied is valid. We test this as follows:
if (valid_email($email))
Again, this is a function that we've written, which is in the data_valid_fns.php library.
Check that the two passwords the user has suggested are the same, as follows:
if ($passwd != $passwd2)
Check that the password is the appropriate length, as follows:
if (strlen($passwd)<6 || strlen($passwd) >16)
In our example, the password should be at least 6 characters long to make it harder to guess, and fewer than 16 characters, so it will fit in the database.
The data validation functions we have used here, filled_out() and valid_email(), are shown in Listing 24.7 and Listing 24.8, respectively.
function filled_out($form_vars)
{
// test that each variable has a value
foreach ($form_vars as $key => $value)
{
if (!isset($key) || ($value == ""))
return false;
}
return true;
}
function valid_email($address)
{
// check an email address is possibly valid
if (ereg("^[a-zA-Z0-9_]+@[a-zA-Z0-9\-]+\.[a-zA-Z0-9\-\.]+$", $address))
return true;
else
return false;
}
The function filled_out() expects to be passed an array of variables—in general, this will be the $HTTP_POST_VARS or $HTTP_GET_VARS arrays. It will check whether they are all filled out, and return true if they are and false if they are not.
The valid_email() function uses the regular expression we developed in Chapter 4, "String Manipulation and Regular Expressions," for validating email addresses. It returns true if an address appears valid, and false if it does not.
After we've validated the input data, we can actually try and register the user. If you look back at Listing 24.6, you'll see that we do this as follows:
$reg_result = register($username, $email, $passwd);
if ($reg_result == "true")
{
// register session variable
$valid_user = $username;
session_register("valid_user");
// provide link to members page
do_html_header("Registration successful");
echo "Your registration was successful. Go to the members page "
."to start setting up your bookmarks!";
do_HTML_URL("member.php", "Go to members page");
}
As you can see, we are calling the register() function with the username, email address, and password that were entered. If this succeeds, we register the username as a session variable and provide the user with a link to the main members page. This is the output shown in Figure 24.5.

The register() function is in the included library called user_auth_fns.php. This function is shown in Listing 24.9.
function register($username, $email, $password)
// register new person with db
// return true or error message
{
// connect to db
$conn = db_connect();
if (!$conn)
return "Could not connect to database server - please try later.";
// check if username is unique
$result = mysql_query("select * from user where username='$username'");
if (!$result)
return "Could not execute query";
if (mysql_num_rows($result)>0)
return "That username is taken - go back and choose another one.";
// if ok, put in db
$result = mysql_query("insert into user values
('$username', password('$password'), '$email')");
if (!$result)
return "Could not register you in database - please try again later.";
return true;
}
There is nothing particularly new in this function—it connects to the database we set up earlier. If the username selected is taken, or the database cannot be updated, it will return false. Otherwise, it will update the database and return true.
One thing to note is that we are performing the actual database connection with a function we have written, called db_connect(). This function simply provides a single location that contains the username and password to connect to the database. That way, if we change the database password, we only need to change one file in our application. The function is shown in Listing 24.10.
function db_connect()
{
$result = mysql_pconnect("localhost", "bm_user", "password");
if (!$result)
return false;
if (!mysql_select_db("bookmarks"))
return false;
return $result;
}
When users are registered, they can log in and out using the regular login and logout pages. We'll build these next.
If users type their details into the form at login.php (see Figure 24.3) and submit it, they will be taken to the script called member.php. This script will log them in if they have come from this form. It will also display any relevant bookmarks to users who are logged in. It is the center of the rest of the application. This script is shown in Listing 24.11.
<?
// include function files for this application
require_once("bookmark_fns.php");
session_start();
if ($username && $passwd)
// they have just tried logging in
{
if (login($username, $passwd))
{
// if they are in the database register the user id
$valid_user = $username;
session_register("valid_user");
}
else
{
// unsuccessful login
do_html_header("Problem:");
echo "You could not be logged in.
You must be logged in to view this page.";
do_html_url("login.php", "Login");
do_html_footer();
exit;
}
}
do_html_header("Home");
check_valid_user();
// get the bookmarks this user has saved
if ($url_array = get_user_urls($valid_user));
display_user_urls($url_array);
// give menu of options
display_user_menu();
do_html_footer();
?>
You might recognize the logic in this script: we are re-using some of the ideas from Chapter 20.
First, we check whether the user has come from the front page—that is, whether he has just filled in the login form—and try to log them in as follows:
if ($username && $passwd)
// they have just tried logging in
{
if (login($username, $passwd))
{
// if they are in the database register the user id
$valid_user = $username;
session_register("valid_user");
}
You can see that we are trying to log him in using a function called login(). We have defined this in the user_auth_fns.php library, and we'll look at the code for it in a minute.
If he is logged in successfully, we register his session as we did before, storing the username in the session variable $valid_user.
If all went well, we then show the user the members page:
do_html_header("Home");
check_valid_user();
// get the bookmarks this user has saved
if ($url_array = get_user_urls($valid_user));
display_user_urls($url_array);
// give menu of options
display_user_menu();
do_html_footer();
This page is again formed using the output functions. You will notice that we are using several other new functions. These are check_valid_user(), from user_auth_fns.php; get_user_urls(), from url_fns.php; and display_user_urls() from output_fns.php. The check_valid_user() function checks that the current user has a registered session. This is aimed at users who have not just logged in, but are mid-session. The get_user_urls() function gets a user's bookmarks from the database, and display_user_urls() outputs the bookmarks to the browser in a table. We will look at check_valid_user() in a moment and at the other two in the section on bookmark storage and retrieval.
The member.php script ends the page by displaying a menu with the display_user_menu() function.
Some sample output as displayed by member.php is shown in Figure 24.6.

We will now look at the login() and check_valid_user() functions a little more closely. The login() function is shown in Listing 24.12.
function login($username, $password)
// check username and password with db
// if yes, return true
// else return false
{
// connect to db
$conn = db_connect();
if (!$conn)
return 0;
// check if username is unique
$result = mysql_query("select * from user
where username='$username'
and passwd = password('$password')");
if (!$result)
return 0;
if (mysql_num_rows($result)>0)
return 1;
else
return 0;
}
As you can see, this function connects to the database and checks that there is a user with the username and password combination supplied. It will return true if there is, or false if there is not or if the user's credentials could not be checked.
The check_valid_user() function does not connect to the database again, but instead just checks that the user has a registered session, that is, that he has already logged in. This function is shown in Listing 24.13.
function check_valid_user()
// see if somebody is logged in and notify them if not
{
global $valid_user;
if (session_is_registered("valid_user"))
{
echo "Logged in as $valid_user.";
echo "<br>";
}
else
{
// they are not logged in
do_html_heading("Problem:");
echo "You are not logged in.<br>";
do_html_url("login.php", "Login");
do_html_footer();
exit;
}
}
If the user is not logged in, the function will tell him that he has to be logged in to see this page, and give him a link to the login page.
You might have noticed that there is a link marked "Logout" on the menu in Figure 24.6. This is a link to the logout.php script. The code for this script is shown in Listing 24.14.
<?
// include function files for this application
require_once("bookmark_fns.php");
session_start();
$old_user = $valid_user; // store to test if they *were* logged in
$result_unreg = session_unregister("valid_user");
$result_dest = session_destroy();
// start output html
do_html_header("Logging Out");
if (!empty($old_user))
{
if ($result_unreg && $result_dest)
{
// if they were logged in and are now logged out
echo "Logged out.<br>";
do_html_url("login.php", "Login");
}
else
{
// they were logged in and could not be logged out
echo "Could not log you out.<br>";
}
}
else
{
// if they weren't logged in but came to this page somehow
echo "You were not logged in, and so have not been logged out.<br>";
do_html_url("login.php", "Login");
}
do_html_footer();
?>
Again, you might find that this code looks familiar. That's because it is based on the code we wrote in Chapter 20.
If a user follows the "Change Password" menu option, he will be presented with the form shown in Figure 24.7.

This form is generated by the script change_passwd_form.php. This is a simple script that just uses the functions from the output library, so we have not included the source for it here.
When this form is submitted, it triggers the change_passwd.php script, which is shown in Listing 24.15.
<?
require_once("bookmark_fns.php");
session_start();
do_html_header("Changing password");
check_valid_user();
if (!filled_out($HTTP_POST_VARS))
{
echo "You have not filled out the form completely.
Please try again.";
display_user_menu();
do_html_footer();
exit;
}
else
{
if ($new_passwd!=$new_passwd2)
echo "Passwords entered were not the same. Not changed.";
else if (strlen($new_passwd)>16 || strlen($new_passwd)<6)
echo "New password must be between 6 and 16 characters. Try again.";
else
{
// attempt update
if (change_password($valid_user, $old_passwd, $new_passwd))
echo "Password changed.";
else
echo "Password could not be changed.";
}
}
display_user_menu();
do_html_footer();
?>
This script checks that the user is logged in (using check_valid_user()), that they've filled out the password form (using filled_out()), and that the new passwords are the same and the right length. None of this is new. If all that goes well, it will call the change_password() function as follows:
if (change_password($valid_user, $old_passwd, $new_passwd)) echo "Password changed."; else echo "Password could not be changed.";
This function is from our user_auth_fns.php library, and the code for it is shown in Listing 24.16.
function change_password($username, $old_password, $new_password)
// change password for username/old_password to new_password
// return true or false
{
// if the old password is right
// change their password to new_password and return true
// else return false
if (login($username, $old_password))
{
if (!($conn = db_connect()))
return false;
$result = mysql_query( "update user
set passwd = password('$new_password')
where username = '$username'");
if (!$result)
return false; // not changed
else
return true; // changed successfully
}
else
return false; // old password was wrong
}
This function checks that the old password supplied was correct, using the login() function that we have already looked at. If it's correct, then the function connects to the database and updates the password to the new value.
In addition to changing passwords, we need to deal with the common situation in which a user has forgotten her password. Notice that on the front page, login.php, we provide a link for users in this situation, marked, "Forgotten your password?" This link will take users to the script called forgot_form.php, which uses the output functions to display a form as shown in Figure 24.8.

This script is very simple, just using the output functions, so we will not go through it here. When the form is submitted, it calls the forgot_passwd.php script, which is more interesting. This script is shown in Listing 24.17.
<?
require_once("bookmark_fns.php");
do_html_header("Resetting password");
if ($password=reset_password($username))
{
if (notify_password($username, $password))
echo "Your new password has been sent to your email address.";
else
echo "Your password could not be mailed to you."
." Try pressing refresh.";
}
else
echo "Your password could not be reset - please try again later.";
do_html_url("login.php", "Login");
do_html_footer();
?>
As you can see, this script uses two main functions to do its job: reset_password() and notify_password(). Let's look at each of these in turn.
The reset_password() function generates a random password for the user and puts it into the database. The code for this function is shown in Listing 24.18.
function reset_password($username)
// set password for username to a random value
// return the new password or false on failure
{
// get a random dictionary word b/w 6 and 13 chars in length
$new_password = get_random_word(6, 13);
// add a number between 0 and 999 to it
// to make it a slightly better password
srand ((double) microtime() * 1000000);
$rand_number = rand(0, 999);
$new_password .= $rand_number;
// set user's password to this in database or return false
if (!($conn = db_connect()))
return false;
$result = mysql_query( "update user
set passwd = password('$new_password')
where username = '$username'");
if (!$result)
return false; // not changed
else
return $new_password; // changed successfully
}
This function generates its random password by getting a random word from a dictionary, using the get_random_word() function and suffixing it with a random number between 0 and 999. The get_random_word() function is also in the user_auth_fns.php library. This function is shown in Listing 24.19.
function get_random_word($min_length, $max_length)
// grab a random word from dictionary between the two lengths
// and return it
{
// generate a random word
$word = "";
$dictionary = "/usr/dict/words"; // the ispell dictionary
$fp = fopen($dictionary, "r");
$size = filesize($dictionary);
// go to a random location in dictionary
srand ((double) microtime() * 1000000);
$rand_location = rand(0, $size);
fseek($fp, $rand_location);
// get the next whole word of the right length in the file
while (strlen($word)< $min_length || strlen($word)>$max_length)
{
if (feof($fp))
fseek($fp, 0); // if at end, go to start
$word = fgets($fp, 80); // skip first word as it could be partial
$word = fgets($fp, 80); // the potential password
} ;
$word=trim($word); // trim the trailing \n from fgets
return $word;
}
To work, this function needs a dictionary. If you are using a UNIX system, the built-in spell checker ispell comes with a dictionary of words, typically located at /usr/dict/words, as it is here. If you are using some other system or do not want to install ispell, don't worry! You can download the word list used by ispell from
http://ficus-www.cs.ucla.edu/ficus-members/geoff/ispell-dictionaries.html
This site also has dictionaries in many other languages, so if you would like a random, say, Norwegian or Esperanto word, you can download one of those dictionaries instead. These files are formatted with each word on a separate line, separated by newlines.
To get a random word from this file, we pick a random location between 0 and the filesize, and read from the file there. If we read from the random location to the next newline, we will most likely only get a partial word, so we skip the line we open the file to, and take the next word as our word by calling fgets() twice.
The function has two clever bits. The first is that, if we reach the end of the file while looking for a word, we go back to the beginning:
if (feof($fp))
fseek($fp, 0); // if at end, go to start
The second is that we can seek for a word of a particular length—we check each word that we pull from the dictionary, and, if it is not between $min_length and $max_length, we keep searching.
Back in reset_password(), after we have generated a new password, we update the database to reflect this, and return the new password back to the main script. This will then be passed on to notify_password(), which will email it to the user.
Let's have a look at the notify_password() function, shown in Listing 24.20.
function notify_password($username, $password)
// notify the user that their password has been changed
{
if (!($conn = db_connect()))
return false;
$result = mysql_query("select email from user
where username='$username'");
if (!$result)
return false; // not changed
else if (mysql_num_rows($result)==0)
return false; // username not in db
else
{
$email = mysql_result($result, 0, "email");
$from = "From: support@phpbookmark \r\n";
$mesg = "Your PHPBookmark password has been changed to $password \r\n"
."Please change it next time you log in. \r\n";
if (mail($email, "PHPBookmark login information", $mesg, $from))
return true;
else
return false;
}
}
In this function, given a username and new password, we simply look up the email address for that user in the database, and use PHP's mail() function to send it to her.
It would be more secure to give users a truly random password—made from any combination of upper and lowercase letters, numbers, and punctuation—rather than our random word and number. However, a password like 'zigzag487'will be easier for our user to read and type than a truly random one. It is often confusing for users to work out whether a character in a random string is 0 or O (zero or capital O), or 1 or l (one or a lowercase L).
On our system, the dictionary file contains about 45,000 words. If a cracker knew how we were creating passwords, and knew a user's name, he would still have to try 22,500,000 passwords on average to guess one. This level of security seems adequate for this type of application even if our users disregard the emailed advice to change it.
| only for RuBoard - do not distribute or recompile |