Learn PHP 7: Object-Oriented Modular Programming using HTML5, CSS3, JavaScript, XML, JSON, and MySQL (2016)

Chapter 7. Authentication

Steve Prettyman

(1)

Georgia, USA

Electronic supplementary material

The online version of this chapter (doi:10.1007/978-1-4842-1730-6_7) contains supplementary material, which is available to authorized users.

“(To Lisa) You got the brains and talent to go as far as you want and when you do, I’ll be right there to borrow money.”

—Bart Simpson

Chapter Objectives/Student Learning Outcomes

After completing this chapter, the student will be able to:

·               Define sessions and explain how they are used for authentication

·               Create a PHP program that authenticates user logon

·               Create a PHP program that registers users

·               Create a PHP program that will allow users to change their passwords

·               Create a PHP program that logs invalid login attempts

·               Create a PHP program that will use current password encryption techniques

Verification and Sessions

No discussion of security would be complete without including user ID/password authentication. The current version of PHP includes many techniques to assist developers in validating users. This chapter looks at one of the more simplistic methods.

Due to the nature of immediate verification of login credentials, the authentication process directly accesses the data source for validation (it does not pass through the business rules tier). Thus, the authentication process is considered a separate tier that is placed on top of the application to provide access. As you will see, only minor changes need to occur in the interface tier programs to restrict access. Most of the coding needed is placed in the authentication tier.

In addition to authentication, levels of access can also be determined during the sign-in process. Not every user needs full access to an application. Some users may only need read access, some may need write access to only the information that pertains to them, and some (administrators) may need full access to the complete application. Each part of the application needs to be able to determine the correct level of access without requesting additional information from the user (beyond the original login to the application).

One login process must allow the users to verify all portions of the application. Each portion of the application needs access to common properties (such as user ID and password) that have been set by the authentication tier for verification of valid access and valid levels of access. PHP provides the ability to store information for an application in server memory by declaring a session. A session is considered to include the complete interaction of the user with the application (such as the complete processes of transferring money from a saving account to a checking account). A session can be established as soon as the user signs in to the application. The session can be closed after the user logs out of the system (or the application times out, or is closed). When a session is closed, all properties stored in the memory of the server are removed by the garbage collector.

While the session is active, properties can be stored and shared throughout the application. Using this process, the user ID and password can be stored in session properties. Each part of the application can then verify that the user has logged in by determining if there are values in theuserid and password properties.

Before you look at the login authentication process, let’s look at how to determine if a user has logged into the system. The examples in this chapter do not include verifications of security access levels. However, the process to determine these levels would use similar code as shown in these examples.

Programming note—The session_start method call must be the first statement at the top of the code. There must be no spaces or code between the <?php tag and session_start. The session_start method produces an HTML header that would not be formed correctly if any code exists before the method call.

<?php

session_start();

if ((!isset($_SESSION['username'])) || (!isset($_SESSION['password']))) {

echo "You must login to access the ABC Canine Shelter Reservation System";

echo "<p>";

echo "<a href='login.php'>Login</a> | <a href='register.php'>Create an account</a>";

echo "</p>";

}

else {

echo "<p>Welcome back, " . $_SESSION['username'] . "</p>";

}

?>

Each program in the interface tier (that the user can access) would include code similar to the previous example. The session_start method, in this example, lets the operating system know that this program is part of an existing session (which is declared in the authentication tier). Each session is identified by the system using a uniquely generated ID. As long as the user (or system) has not closed the session, the session ID will be attached to any program, called by the user, which includes the session_start method. This allows the program to access all properties related to the current session.

The PHP isset method (in an if statement) can determine if values exist in the username and password properties. If values do not exist, it indicates that the user has not been authenticated. Session properties are retrieved (and set) using $_SESSION. In the previous example, if either of the properties is not set, the user is provided links to the login page (login.php) or the register page (register.php). If both properties are set, the user is welcomed to the system. The user has no choice but to log in to access the program. In addition, as mentioned in previous chapters, for more secure programs, the IP address of the user’s machine, and the calling program can be determined to provide extra assurance that the user is authorized.

Example 7-1. The lab.php file with user ID/password verification

<?php

session_start();

if ((!isset($_SESSION['username'])) || (!isset($_SESSION['password']))) {

echo "You must login to access the ABC Canine Shelter Reservation System";

echo "<p>";

echo "<a href='login.php'>Login</a> | <a href='register.php'>Create an account</a>";

echo "</p>";

}

else

{

echo "<p>Welcome back, " . $_SESSION['username'] . "</p>";

?>

<!DOCTYPE html>

<html lan="en">

<head>

<title>Dog Object</title>

<script src="get_breeds.js"></script>

<script src="validator.js"></script>

<style type="text/css">

#JS { display:none; }

</style>

<script>

function checkJS() {

document.getElementById('JS').style.display = "inline";

}

</script>

</head>

<body onload="checkJS();">

<h1>Dog Object Creater</h1>

<div id="JS">

<form method="post" action="e5adog_interface.php" onSubmit="return validate_input(this)">

<h2>Please complete ALL fields. Please note the required format of information.</h2>

Enter Your Dog's Name (max 20 characters, alphabetic) <input type="text" pattern="[a-zA-Z]*"  title="Up to 20 Alphabetic Characters" maxlength="20" name="dog_name" id="dog_name" required/><br /><br />

Select Your Dog's Color:<br />

<input type="radio" name="dog_color" id="dog_color" value="Brown">Brown<br />

<input type="radio" name="dog_color" id="dog_color" value="Black">Black<br />

<input type="radio" name="dog_color" id="dog_color" value="Yellow">Yellow<br />

<input type="radio" name="dog_color" id="dog_color" value="White">White<br />

<input type="radio" name="dog_color" id="dog_color" value="Mixed" checked >Mixed<br /><br />

Enter Your Dog's Weight (numeric only) <input type="number" min="1" max="120" name="dog_weight" id="dog_weight" required /><br /><br />

<script>

AjaxRequest('e5dog_interface.php');

</script>

<input type="hidden" name="dog_app" id="dog_app" value="dog" />

Select Your Dog's Breed <div id="AjaxResponse"></div><br />

<input type="submit" value="Click to create your dog" />

</form>

</div>

<noscript>

<div id="noJS">

<form method="post" action="e5adog_interface.php">

<h2>Please complete ALL fields. Please note the required format of information.</h2>

Enter Your Dog's Name (max 20 characters, alphabetic) <input type="text" pattern="[a-zA-Z ]*"title="Up to 20 Alphabetic Characters" maxlength="20" name="dog_name" id="dog_name" required/><br /><br />

Select Your Dog's Color:<br />

<input type="radio" name="dog_color" id="dog_color" value="Brown">Brown<br />

<input type="radio" name="dog_color" id="dog_color" value="Black">Black<br />

<input type="radio" name="dog_color" id="dog_color" value="Yellow">Yellow<br />

<input type="radio" name="dog_color" id="dog_color" value="White">White<br />

<input type="radio" name="dog_color" id="dog_color" value="Mixed" checked >Mixed<br /><br />

Enter Your Dog's Weight (numeric only) <input type="number" min="1" max="120" name="dog_weight" id="dog_weight" required /><br /><br />

Enter Your Dog's Breed (max 35 characters, alphabetic) <input type="text" pattern="[a-zA-Z ]*" title="Up to 15 Alphabetic Characters" maxlength="35" name="dog_breed" id="dog_breed" required /><br />

<input type="hidden" name="dog_app" id="dog_app" value="dog" />

<input type="submit" value="Click to create your dog" />

</form>

</div>

</noscript>

</body>

</html>

<?php

}

?>

In Example 7-1, the verification code is placed at the top of the program. The else statement must include all the code to execute if the user is signed into the system. Since the code for this program is HTML (and CSS) code, the if statement must be wrapped around the existing code. The closing bracket (s) of the else statement is shifted to the bottom of the code (after all HTML tags).

PHP allows you to close your PHP code (via ?>) and reopen your PHP code (via <?php) as many times as required. In this example, the PHP code is closed at the top of the program (in the else statement) just before the closing bracket. The PHP code is then reopened at the bottom of the code to include a single closing bracket, which closes the PHP else statement. This wraps the else statement around all the existing code. The users can now only access this section of the code if they are logged in.

Since PHP code is now included in the lab program, the file ending must be changed from .html to .php. Otherwise the server would not execute the PHP code.

Now let’s look at how you can populate the session properties by creating a login program. First let’s look at the HTML to request information from the users.

<form method="post" action="">

Username: <input type="text" pattern=".{8,}" title="Userid must contain eight or more characters." name="username" id="username" required/><br />

Password: <input type="password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters." name="password" id="password" required /><br />

<input type="submit" value="Login">

</form>

HTML5 does not include a minimum length parameter. However, the pattern parameter can be used (with regular expressions) to establish a minimum size. In the username tag in the previous example, the pattern ".{8,}" requires at least eight characters be entered by the users. For password security, a more complicated pattern is needed. In the password example, in addition to the minimum requirement of eight characters, at least one number (?=.*\d), one uppercase letter (?=.*[A-Z]), and one lowercase letter (?=.*[a-z]) are required.

Security and performance—The HTML filtering provided is used to inform the users of any typos that may have occurred. In the login process you are not storing information; you are comparing information to what has already been stored. You don’t have to be concerned with any possible harmful information being passed into the text boxes. Any harmful information would not match the valid information that’s stored. The user would receive an invalid user ID/password message.

// validate process not shown

$_SESSION['username'] = $_POST['username'];

$_SESSION['password'] = $_POST['password'];

// Redirect the user to the home page

header("Location: http://www.asite.com/lab.php ");

Assuming you have validated the information against a list of valid user IDs and passwords (you will look at that process soon), you can pass the valid user ID and password information into session variables. Then the PHP header method can be used to redirect the application to the next program to execute (lab.php). As long as lab.php includes the session_start method (as shown previously), it will have access to the session variables.

<?php

session_start();

if ((!isset($_POST['username'])) || (!isset($_POST['password'])))

{

?>

<form method="post" action="">

Username: <input type="text" pattern=".{8,}" title="Userid must contain eight or more characters." name="username" id="username" required/><br />

Password: <input type="password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters."

name="password" id="password" required /><br />

<input type="submit" value="Login">

</form>

<?php

} else {

// validate process not shown

  $_SESSION['username'] = $_POST['username'];

  $_SESSION['password'] = $_POST['password'];

  // Redirect the user to the home page

  header("Location: http://www.asite.com/lab.php ");

  }

?>

Putting the pieces together requires an if statement to determine if the user has entered the user ID and password. If the user has not done so, the HTML code to request them is displayed. If the information has been entered (and is valid using the HTML5 pattern expressions shown) theelse portion of the statement will execute (storing the values in the session variables and calling the lab.php program). This provides you with the basic shell of accepting the user ID and password, verifying they exist, and calling the program in the interface tier if they do exist. Of course, you need to authenticate the user ID and password before calling the program.

Programming note—The server variables PHP_AUTH_USER and PHP_AUTH_PW can be used for user ID and password validation, instead of using session variables.

header('WWW-Authenticate: Basic realm="ABC Canine"');

    header('HTTP/1.0 401 Unauthorized');

Unauthorized header messages can be created if the user has not entered a user ID/password or a valid user ID/password. This will automatically cause the system to request the user enter a user ID/password. This technique is pretty straightforward. However, there have been some reports, in the past, of browsers not functioning properly with this technique. Besides, creating your own technique allows you to design the login screen with the same style as the rest of your web site.

For more information, visit:

http://php.net/manual/en/features.http-auth.php

$valid_useridpasswords = array ("sjohnson" => "N3working");

$valid_userids = array_keys($valid_useridpasswords);

$userid = $_SESSION['username'];

$password = $_SESSION['password'];

$valid = (in_array($userid, $valid_userids)) && ($password == $valid_useridpasswords[$userid]);

If($valid) { header("Location: http://www.asite.com/lab.php ");}

There are several ways you can authenticate user IDs and passwords. If you are creating a system that does not require user IDs and passwords to change, you could use arrays. In the previous example the $valid_useridpasswords associate array contains the combination of valid user IDs and passwords. The PHP method array_keys places all keys (in this example the user IDs) into a separate array ($valid_userids). After the session variables have been placed in $userid and $password, the PHP in_array method is used to determine if the correct combination of user ID and password exists. in_array determines if the user ID exists in the array. Then the user ID is used as the subscript to pull the password from the valid_useridpasswords array and compare it to the value in $password. If the user ID exists and the passwords are the same, then everything is valid.$valid will contain TRUE. If either (or both) are not valid, $valid will contain FALSE. If $valid is TRUE, the application redirects to the lab.php program.

Technically properties in a session are secured from any access outside the session. However, there have been reported instances, in the past, of hacker programs breaking this security and accessing session information. If the user ID and password, in this example, are stored in session variables and passed across the Internet to another program, hackers might gain access to the information.

If the user ID and password are externally stored in a file or database, the information will also travel outside the program. The program will no longer have control over the security of these items once they reside in the file or database. This could allow hackers access to the information. Security, as mentioned, has to be a team effort among the programmer, data administrator, and network administrator.

It is common practice to encrypt the password to reduce the chance that hackers will discover the authentication information (or any other secure information). Many PHP books demonstrate the use of the MD5 hash technique. However, over the last several years vulnerabilities have been discovered in this encyption style.

PHP 5.5 included the method password_hash, which will be adjusted over time to use the most secure encyption hashing techniques avaliable.

Programming note—Caution should be used when storing the encrypted version of the password. The size of the resulting encryption will increase with new hash versions. A size of 255 characters is likely to be large enough for the many years. The number of milliseconds needed for this hash increases with the size and type of the encryption. Advanced programmers may want to do some testing on their servers for time costs.

Visit http://php.net/manual/en/function.password-hash.php for more information.

Programming note—You cannot do a simple comparison with the hashed password created by PHP’s password_hash method. The hash produced includes the encryption type, a salt value, and the hashed password.

You only need to replace one line of code in the example to verify the password. You can replace

$valid = ((in_array($userid, $valid_userids)) && ($password == $valid_useridpasswords[$userid]));

with

$valid =( (in_array($userid, $valid_userids)) && (password_verify($password, $valid_useridpasswords[$userid]));

If you placed the encypted password in the $valid_useridpasswords array, the validation technique would not require any other changes. The PHP password_verify method will encrpt the password provided by the user and compare it to the existing encyrpted password. If they match, it will return TRUE.

When using XML or JSON files, you can use the same logic used in the constructor for the dogdata.php program from Chapter 6, to retrieve the valid user ID and password information. The only changes needed are to the if statement, which determines the location of the user ID and password file, and to the last line in the constructor to place the array produced to $valid_useridpasswords instead of dogs_array.

<users>

<user>

<userid>Fredfred</userid>

<password>$2y$10$VosI32FejL.bOMaCjGbBp.Jre6Ipa.tLYQrVqj9kiVpef5zZ25qQK</password>

</user>

<user>

<userid>Petepete</userid>

<password>$2y$10$FdbXxIVXmVOHtaBNxB8vzupRBJFCqUyOTJXrlpNdrL0HKQ/U.jFHO</password>

</user>

</users>

Assuming the XML is in a similar format as the dog data XML file (as shown), you can use similar logic to retrieve the information.

$valid_useridpasswords = json_decode($json,TRUE);

$userid = $_POST['username'];

$password = $_POST['password'];

    foreach($valid_useridpasswords as $users)

{

        foreach($users as $user)

        {

            $hash = $user['password'];

        if((in_array($userid, $user)) && (password_verify($password,$hash)))

        {

                $_SESSION['username'] = $userid;

                $_SESSION['password'] = $hash;

                header("Location: lab.php");

        }

}

The array created using the json_decode method is in a similar format to the array created from the dog_data XML file. It requires two foreach loops, one to loop through the “users” array, and the other to loop through the “user” arrays. The in_array method can then be used to determine if the user ID exists in the user array. If it does, the password is compared to the hashed password using the PHP method password_verify. This method uses the first part of the hashed password to retrieve the information on the encryption technique and the salt value. The salt value is an automatically generated value that is used to produce the hashed password. If the passwords match, the user ID and hashed password ($hash) are saved as session variables. The main program is then called (see Example 7-1).

Note

In PHP 5.5, you could adjust the salt value. In PHP 7, this option was depreciated as it was deemed an unnecessary use of system resources.

Example 7-2. The login.phpfile with XML user ID/password verification

<?php // same code as constructor from chapter 6 with some minor changes

session_start();

try {

$user_log_file = "user.log";

if ((isset($_POST['username'])) || (isset($_POST['password'])))

{

                libxml:use_internal_errors(true);

                $xmlDoc = new DOMDocument();

                if ( file_exists("e7dog_applications.xml") )

                {

                        $xmlDoc->load( 'e7dog_applications.xml' );

                        $searchNode = $xmlDoc->getElementsByTagName( "type" );

                        foreach( $searchNode as $searchNode )

                        {

                                $valueID = $searchNode->getAttribute('ID');

                                if($valueID == "UIDPASS") // changed value to UIDPASS

                                {

                                $xmlLocation = $searchNode->getElementsByTagName( "location" );

                                // change $this->dog_data_xml to dog_data_xml

                                $dog_data_xml = $xmlLocation->item(0)->nodeValue;

                                break;

                                }

}

        }

else

        {

                throw new Exception("Dog applications xml file missing or corrupt");

        }

        $xmlfile = file_get_contents($dog_data_xml);

        $xmlstring = simplexml:load_string($xmlfile);

        if ($xmlstring === false) {

                $errorString = "Failed loading XML: ";

                foreach(libxml:get_errors() as $error) {

                        $errorString .= $error->message . " " ;  }

                throw new Exception($errorString); }

        $json = json_encode($xmlstring);

        // changed array name to $valid_useridpasswords

        $valid_useridpasswords = json_decode($json,TRUE);

// ...... code to verify userid and password ....

        $userid = $_POST['username'];

        $password = $_POST['password'];

            foreach($valid_useridpasswords as $users)        {

                foreach($users as $user) {

                    $hash = $user['password'];

                if((in_array($userid, $user)) && (password_verify($password,$hash))) {

                        $_SESSION['username'] = $userid;

                        $_SESSION['password'] = $password;

                        $login_string = date('mdYhis') . " | Login | " . $userid . "\n";

                        error_log($login_string,3,$user_log_file);

                        header("Location: e7lab.php");

        } }  } }

}

   catch(Exception $e)

   {

        echo $e->getMessage();

   }

// code below executes if the user has not logged in or if it is an invalid login.

?>

<form method="post" action="">

Userid must contain eight or more characters.<br/>

Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters.<br />

Username: <input type="text" pattern=".{8,}" title="Userid must contain eight or more characters." name="username" id="username" required/><br />

Password: <input type="password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters."

name="password" id="password" required /><br />

<input type="submit" value="Login">

</form>

In addtion to the code mentioned, Example 7-2 also includes a try catch block to catch the exceptions thrown and a call to a user log file to record successful logs in to the system.

JSON Data

To use JSON data instead of XML data, Example 7-2 would only need to include the changes shown in Chapter 6. The userid and password JSON data would also need to be formatted as shown.

{"user":

[

{"userid":"Fredfred","password":"$2y$10$VosI32FejL.bOMaCjGbBp.Jre6Ipa.tLYQrVqj9kiVpef5zZ25qQK"},

{"userid":"Petepete","password":"$2y$10$FdbXxIVXmVOHtaBNxB8vzupRBJFCqUyOTJXrlpNdrL0HKQ\/U.jFHO"}

] }

MySQL Data

It is more common and usually more secure to store user ID and password information in a database. Databases can be secured using user IDs and passwords, which can also include levels of access (read only, read, and write) to the information. Even with this level of security, the password should still be encrypted.

Very few minor changes are needed to the MySQL constructor example from Chapter 6 to accomplish authentication.

$mysqli =mysqli_connect($server, $db_username, $db_password, $database);

if (mysqli_connect_errno())

  {

          throw new Exception("MySQL connection error: " . mysqli_connect_error());

  }

$sql="SELECT * FROM Users"; // Change the table used

$result=mysqli_query($con,$sql);

If($result===null)

{

        throw new Exception("No records retrieved from Database");

}

$valid_useridpasswords = mysqli_fetch_assoc($result); // change the array used

mysqli_free_result($result);

mysqli_close($con);

This example code will pull the information from a table in the database (Users) and place the information in an associate array (via the mysqli_fetch_assoc method). If the fields in the table are the same as the tag names from the XML file (userid and password), the associate array built will be similar to the array built using the code from Example 7-2. All previous code "// ...... code to verify userid and password ...." is replaced by the code shown here. No code below the statement should need adjusting.

Do It

1.

Create a conversion program to determine the encrypted version of a password using the PHP method password_hash (see http://php.net/manual/en/function.password-hash.php ).

2.

Download the example files from this section. Add XML records to the uidpass file that could be used for access permissions (read only or read/write) and levels (user or administrator). Test to verify that the new uidpass file works correctly with the existing code. Make any necessary code changes to make it compatible.

3.

Download the example files from this section. Add code to the program to limit attempts to log in with a bad password to three. Record any invalid attempt to log in (after three tries) in the user log.

Registration

In addition to authorizing user logons, most systems allow the users to create their own user IDs and passwords. By default, the self-created IDs are given the lowest priorities. An administrator can then go in and increase the level of privileges once the ID has been created. Some sites allow non-registered users (users who are not logged in).

Non-registered users should only be given read-only access to non-privileged information. Non-registered users are much more of a security risk because it would be difficult to determine their identity during a security breach. PHP provides the ability to retrieve the user’s IP address using $_SERVER['REMOTE_ADDR']. This might provide some ability to trace the user. However, many users use free public access points, which generate random IP addresses. If the one of these points is used, it will be much more difficult to track them down.

In addition, providing the opportunity for users to create user IDs and passwords allows the program to gather additional information (such as name and e-mail) that can help with security, and also provide easy ability to promote the web site to users of the site. The success rate of selling what is offered on the site will be much higher to customers who are already familiar with the site. To encourage users to create user IDs and passwords, the site should offer them some benefit (such as access to the help desk) that is not available to visitors.

The registration page will, in many ways, work similarly to the login page. However, any valid user ID or password entered will be stored (instead of compared). Because the application will be updating the list of valid IDs, the application must validate the information before it is updated. You can use some of the techniques that you used previously when validating the dog class properties. First, as the user enters a user ID and password (which must pass the HTML5 validation shown previously), it is passed to a PHP program. Even though it is considered to be secure inside a session, the information will travel from the HTML form to the PHP code. The information should be validated again.

if ((isset($_POST['username'])) || (isset($_POST['password'])))

{

$userid = $_POST['username'];

$password = $_POST['password'];

if (!(preg_match("/^.*(?=.{8,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*$/", $password)) || (!(strlen($userid) >= 8)))

{

        throw new Exception("Invalid Userid and/or Password Format");

}

else

This if statement uses the PHP function preg_match to determine if the format of the password contains one uppercase letter, one lowercase letter, one number, and at least eight characters. Notice that the regular expression format is the same as used with the HTML5 code (the order of the expression has changed, but it still contains the same information). The PHP strlen method also checks the user ID to determine that it has eight or more characters.

If either validation does not pass, the program raises an Exception. If both pass, the else part of the statement executes to encrypt the password and store the information.

$password = password_hash($password, PASSWORD_DEFAULT);

$input = file_get_contents($dog_data_xml);

$find = "</users>";

$newupstring = "<user>\n<userid>" . $userid . "</userid>\n<password>" . $password;

$newupstring .= "</password>\n</user>\n</users>";

$find = preg_quote($find,'/');

$output = preg_replace("/^$find(\n|\$)/m","",$input);

$output = $output . $newupstring;

file_put_contents($dog_data_xml,$output);

$login_string = date('mdYhis') . " | New Userid | " . $userid . "\n";

error_log($login_string,3,$user_log_file);

header("Location: e7login.php");

Before inserting the password into the XML file, it must be encyrpted (hashed). The PHP method password_hash will convert the password to the format shown previously.

Programming note— password_hash has many different options and configurations available for the advanced developer. For more information,

visit http://php.net/manual/en/function.password-hash.php .

file_get_contents dumps the contents of the XML user ID/password file into $input. preg_quote will place backslashes next to any special characters in $find (such as the backslash contained in /users) to keep PHP from trying to interpret those characters as part of a regular expression.preg_replace will use the regular expression /^$find(\n|\$)/m to search for </users> with and without (\n) at the end. Since records in files are determined by the newline character, this will ensure that you find </users> at either the end of a line in the file or as part of a line in the file. When</users> is found in the file (the contents of the file are in $input), it is replaced by "" (empty string). preg_replace will also attempt to place backslashes in any string existing in the second parameter (where the "" is currently located). If the $newupstring is placed in that parameter, the encrypted password would be modified by preg_replace. This would cause the passwords to not verify, even if the one entered by the user is correct.

Therefore, the contents of $newupstring (the new user info) are appended to $output. The $output string is then used to replace all contents of the user ID/password XML file. Once this information has been saved successfully, the user log is updated to indicate the creation of a new user ID, and the user is redirected to the login screen to sign in to the application with the new user ID and password.

Alternatively, the previous process of loading the file into an associative array, updating the associative array, and then loading the associate array back into the XML file could have been used. However, it would have taken more code and is unnecessary because this process is only used once in the application. The program will not make multiple attempts to update the user ID and password file with the same user.

Example 7-3. The registration.php file

<?php

session_start();

$user_log_file = "user.log";

try

{

if ((isset($_POST['username'])) || (isset($_POST['password'])))

{

$userid = $_POST['username'];

$password = $_POST['password'];

if (!(preg_match("/^.*(?=.{8,})(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).*$/", $password)) || (!(strlen($userid) >= 8)))

{

throw new Exception("Invalid Userid and/or Password Format");

}

else

{

        libxml:use_internal_errors(true);

        $xmlDoc = new DOMDocument();

        if ( file_exists("e7dog_applications.xml") )

        {

        $xmlDoc->load( 'e7dog_applications.xml' );

        $searchNode = $xmlDoc->getElementsByTagName( "type" );

                foreach( $searchNode as $searchNode )

                {

                        $valueID = $searchNode->getAttribute('ID');

                        if($valueID == "UIDPASS")

                        {

                        $xmlLocation = $searchNode->getElementsByTagName( "location" );

                        $dog_data_xml = $xmlLocation->item(0)->nodeValue;

                        break;

                        }

                }

        }

        else

        {

                throw new Exception("Dog applications xml file missing or corrupt");

        }

} else {

        throw new Exception("Dog applications xml file missing or corrupt");

        }

        $password = password_hash($password, PASSWORD_DEFAULT);

        $input = file_get_contents($dog_data_xml);

        $find = "</users>";

        $newupstring = "<user>\n<userid>" . $userid . "</userid>\n<password>" . $password . "</password>\n</user>\n</users>";

        $find_q = preg_quote($find,'/');

        $output = preg_replace("/^$find_q(\n|\$)/m",$newupstring,$input);

        file_put_contents($dog_data_xml,$output);

        $login_string = date('mdYhis') . " | New Userid | " . $userid . "\n";

        error_log($login_string,3,$user_log_file);

        header("Location: e7login.php");

        } } }

   catch(Exception $e)   {   echo $e->getMessage(); }

?>

<form method="post" action="">

Userid must contain eight or more characters.<br/>

Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters.<br />

Username: <input type="text" pattern=".{8,}" title="Userid must contain eight or more characters." name="username" id="username" required/><br />

Password: <input type="password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters."

name="password" id="password" required /><br />

<input type="submit" value="submit">

</form>

JSON Data

JSON data would require just a couple of slight changes.

{"user":

[

{"userid":"Fredfred","password":"$2y$10$VosI32FejL.bOMaCjGbBp.Jre6Ipa.tLYQrVqj9kiVpef5zZ25qQK"},

{"userid":"Petepete","password":"$2y$10$FdbXxIVXmVOHtaBNxB8vzupRBJFCqUyOTJXrlpNdrL0HKQ\/U.jFHO"}

] }

The data ends with a combination of ]}, which does not occur anywhere else. $find can be set to this value ($set = "]}";). The $newupstring value can also be changed to:

$newupstring = ',{"userid":"' . $userid . '","password":"' . $password . '"}\n]}';

These two changes (along with the previous changes) would update a JSON file with a new user ID/password combination.

MySQL Data

MySQL would require a few more changes. After the password is encyrpted using password_hash, the database can be opened, the record inserted, and then the database can be closed.

$password = password_hash($password, PASSWORD_DEFAULT);

$mysqli =mysqli_connect($server, $db_username, $db_password, $database);

if (mysqli_connect_errno())

  {

          throw new Exception("MySQL connection error: " . mysqli_connect_error());

  }

$sql="INSERT INTO Users (userid, password) VALUES('" . $userid . "','" . $password . "');";

$result=mysqli_query($con,$sql);

If($result===null)

{

           throw new Exception("Userid/Password not added to Database");

}

mysqli_close($con);

$login_string = date('mdYhis') . " | New Userid | " . $userid . "\n";

error_log($login_string,3,$user_log_file);

header("Location: e7login.php");

Logging In

In addition to providing the users with the ability to create their own user IDs and passwords, an application should also provide them the ability to change their passwords. The ability to provide a date limit to expire passwords is very beneficial to increase security. Everytime the user logs on, a comparision can be made on this value. If the current date is more than xx days older than the date saved, the user would be required to change the password.

Since password sniffer programs will try to guess passwords, it is also a good idea to limit to the number of attempts to sign in with the right user ID and password combination. This would reduce the chances that a password sniffing program could generate the correct combination. It is important not allow a valid signin for a period of time after the maximum amount of attempts have been made. Even though this is frustrating to the user, it reduces the chances that a password sniffing program would discover the right combination. If the program does not know that the attempts have timed out, it will receive invalid user ID/password messages, even if it guessed the right combination during the timeout period. These adjustments will require additional fields to the user ID/password file (or database).

<users>

<user>

<userid>Fredfred</userid>

<password>$2y$10$VosI32FejL.bOMaCjGbBp.Jre6Ipa.tLYQrVqj9kiVpef5zZ25qQK</password>

<datestamp>2015-09-03</datestamp>

<attempts>0</attempts>

<lastattempt>08052015044229</lastattempt>

<validattempt>08052015045431</validattempt>

</user>

<user>

<userid>Poppoppop</userid>

<password>$2y$10$C1jXhTl0myamuLKhZxK5m.4X4TVcdeFbeLSBIA7l4fx6tUnC8vrg6</password>

<datestamp>2015-06-04</datestamp>

<attempts>1</attempts>

<lastattempt>08062015113200</lastattempt>

<validattempt>08062015113038</validattempt>

</user>

</users>

The $newupstring can be adjusted in the registration.php program (Example 7-3) to add the authentication fields.

$newupstring = "<user>\n<userid>" . $userid . "</userid>\n<password>" . $hashed_password . "</password>\n";

$newupstring .= "<datestamp>" . date('Y-m-d', strtotime('+30 days')) . "</datestamp>\n";

$newupstring .= "<attempts>0</attempts>\n<lastattempt>" . date('mdYhis') . "</lastattempt>\n";

$newupstring .= "<validattempt>" . date('mdYhis') . "</validattempt>\n</user>\n</users>";

The PHP method strtotime will parse any standard date and time format and attempt to convert it to the UNIX date time format (which PHP uses). In this example, the method provides the ability to add 30 days to the current date, which in turn will be used to determine if a password has expired. Alternatively, an expired date (such as the day before the current date) could be placed in this field when user IDs are created in bulk (such as populating student IDs in a course management system). This would force the users to change the password the first time they sign in to the system. The expire date is stored in datestamp for use when the user logs in to the system.

The attempts tag, in the example, will record how many times the user tries to sign in with a bad user ID password combination (reset to zero when a valid login occurs). If the date and time in lastattempt are in five minutes, and the value of attempts is 3 or greater, the user must wait until more than five minutes has expired since the last attempt to log in. As with most login systems, even if the user logs in with valid information, the last invalid login must be five or more minutes ago. The last valid login date and time is also recorded in the validattempt tags. Although this is not used for authentication in this example, it is important to keep track of all valid logins.

To keep the code as simple as possible in the main section of the program, the code that looks up the location of the user ID and password file has been moved to the method retrieve_useridpasswordfile. Saving the data in the XML file has also been moved to the method saveupfile. No changes (except for the addition of more XML tags as mentioned previously) have occurred in the code for these methods.

Example 7-4. The login.php file with password timeout and three tries timeout

<?php

session_start();

$user_log_file = "user.log";

$passed = FALSE;

function saveupfile($dog_data_xml,$valid_useridpasswords)

{

$xmlstring = '<?xml version="1.0" encoding="UTF-8"?>';

           $xmlstring .= "\n<users>\n";

         foreach($valid_useridpasswords as $users)

        {

        foreach($users as $user)

        {

        $xmlstring .="<user>\n<userid>" . $user['userid'] . "</userid>\n";

        $xmlstring .="<password>" . $user['password'] . "</password>\n";

        $xmlstring .="<datestamp>" . $user['datestamp'] . "</datestamp>\n";

        $xmlstring .= "<attempts>" . $user['attempts'] . "</attempts>\n";

        $xmlstring .= "<lastattempt>" . $user['lastattempt'] . "</lastattempt>\n";

        $xmlstring .= "<validattempt>" . $user['validattempt'] . "</validattempt>\n</user>\n";

        }

    }

        $xmlstring .= "</users>\n";

$xmlstring .= "</users>\n";

$new_valid_data_file = preg_replace('/[0-9]+/', '', $dog_data_xml);

// remove the previous date and time if it exists

$oldxmldata = date('mdYhis') . $new_valid_data_file;

if (!rename($dog_data_xml, $oldxmldata))

        {

           throw new Exception("Backup file $oldxmldata could not be created.");

        }

file_put_contents($new_valid_data_file,$xmlstring);

}

function retrieve_useridpasswordfile()

{

$xmlDoc = new DOMDocument();

        if ( file_exists("e7dog_applications.xml") )

        {

        $xmlDoc->load( 'e7dog_applications.xml' );

        $searchNode = $xmlDoc->getElementsByTagName( "type" );

                foreach( $searchNode as $searchNode )

                {

                        $valueID = $searchNode->getAttribute('ID');

                        if($valueID == "UIDPASS")

                        {

                        $xmlLocation = $searchNode->getElementsByTagName( "location" );

                        $dog_data_xml = $xmlLocation->item(0)->nodeValue;

                        break;

                        }

                }

        }

else

        {

                throw new Exception("Dog applications xml file missing or corrupt");

        }

        return $dog_data_xml;

}

try {

if ((isset($_POST['username'])) && (isset($_POST['password'])))

{

        libxml:use_internal_errors(true);

        $dog_data_xml = retrieve_useridpasswordfile();

        $xmlfile = file_get_contents($dog_data_xml);

        $xmlstring = simplexml:load_string($xmlfile);

        if ($xmlstring === false) {

                $errorString = "Failed loading XML: ";

                foreach(libxml:get_errors() as $error) {

                        $errorString .= $error->message . " " ;  }

                throw new Exception($errorString); }

        $json = json_encode($xmlstring);

        $valid_useridpasswords = json_decode($json,TRUE);

        $userid = $_POST['username'];

        $password = $_POST['password'];

        $I = 0;

        $passed = FALSE;

    foreach($valid_useridpasswords as $users)

        {

        foreach($users as $user)

        {

    if (in_array($userid, $user))        {

        $hash = $user['password'];

        $currenttime = strtotime(date('Y-m-d'));

        $stamptime = strtotime($user['datestamp']);

if ($currenttime > $stamptime)        {

// password expired force password change

                header("Location: e7changepassword.php");

                }

        if (($user['attempts'] < 3) || ( date('mdYhis', strtotime('-5 minutes')) >= $user['lastattempt']))

                        {

                                $hash = $user['password'];

                                if(password_verify($password,$hash))

                                {

                                $passed = TRUE;

                                $valid_useridpasswords['user'][$I]['validattempt'] = date('mdYhis');

                                 // shows last time successful login

                                $valid_useridpasswords['user'][$I]['attempts'] = 0;

// successful login resets to zero

                                $_SESSION['username'] = $userid;

                                $_SESSION['password'] = $password;

                                saveupfile($dog_data_xml,$valid_useridpasswords);

// save changes before header call

                                $login_string = date('mdYhis') . " | Login | " . $userid . "\n";

                                error_log($login_string,3,$user_log_file);

                                header("Location: e7lab.php");

                                }

                                else {

                                $valid_useridpasswords['user'][$I]['lastattempt'] = date('mdYhis');

// last attempted login

                                } } }

                $I++;

        } }

                // drops to here if not valid password/userid or too many attempts

                if (!$passed) {

                        $I--;

                        echo "Invalid Userid/Password";

                        $valid_useridpasswords['user'][$I]['attempts'] = $user['attempts'] + 1;

                        // add 1 to attempts

                        // if not successful must save the values

                        saveupfile($dog_data_xml,$valid_useridpasswords);

                } } }

   catch(Exception $e)

   {   echo $e->getMessage(); }

?>

form method="post" action="">

Userid must contain eight or more characters.<br/>

Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters.<br />

Username: <input type="text" pattern=".{8,}" title="Userid must contain eight or more characters." name="username" id="username" required/><br />

Password: <input type="password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters."

name="password" id="password" required /><br />

<input type="submit" value="Login">

</form>

In Example 7-4, an if statement checks the current date/time with the date/time saved in datestamp. If the current date/time is more than 30 days beyond the value in datestamp, the password change (changepassword) program is called. The next if statement determines if the user has had fewer than three invalid attempts to log in, and it has been more than five minutes since the last attempt. If this is the case, the PHP method password_verify will compare the password entered by the user with the password contained in the XML file. If the passwords match, the validattempts value will be updated with the date/time, the attempts will be reset to 0, and the user ID and password will be saved in session variables. The changes to the XML file are then saved. In addition, the user login is recorded in the log file. If the passwords do not match, the lastattempt value is updated with the current date/time.

Since a valid login or an expired password will cause the application to redirect to a different program with the PHP header method, the program will only drop to the last if statement if the user ID and password combo is not valid, or if there have been too many attempts in five minutes. When this occurs, the "invalid /password" message is displayed, and the number of attempts is increased by 1. The changes to the XML file are then saved. The program will then continue to display the user ID and password boxes with the "invalid userid/password" message. As noted, even a valid user ID/password combo that’s has been entered in five minutes or three or more invalid entries (sequentially) is rejected.

JSON Data

JSON data would require some changes to accommodate the additional fields.

{"user":[

{"userid":"Fredfred","password":"$2y$10$VosI32FejL.bOMaCjGbBp.Jre6Ipa.tLYQrVqj9kiVpef5zZ25qQK","datestamp":"2015-09-03","attempts":"0","lastattempt":"08052015044229","validattempt":"08052015045431"},

{"userid":"Poppoppop","password":"$2y$10$C1jXhTl0myamuLKhZxK5m.4X4TVcdeFbeLSBIA7l4fx6tUnC8vrg6","datestamp":"2015-09-04","attempts":"2","lastattempt":"08062015011347","validattempt":"08062015113038"}

]}

The $newupstring would also require changes due to these new fields. The location of some of the code would also move to methods, as mentioned earlier.

$newupstring = ',{"userid":"' . $user['userid'] . '","password":"' . $user['password'] . '","';

$newupstring .= 'datestamp":"' . $user['datestamp'] . '","attempts":"' . $user['attempts'] . "',"';

$newupstring .= 'lastattempt":"' . $user['lastattempt'] . '","validattempt":"' . $user['validattempt'] .'"';

$newupstring .= '"}\n]}';

MySQL Data

MySQL login code requires the UPDATE statement (instead of an INSERT statement, as seen in the registration code) to update any fields that have changed. In addition, the clean_input method from Chapter 4 should to be used to remove any harmful PHP and SQL statements from the useridfield. This will help reduce the chance of SQL injection occuring, which could cause the SQL statement to change more than just the required fields and record(s). For example, if the $user['userid'] field contained '*', all records would be updated instead of just one record with a valid user ID.

The user ID could also be validated as existing in the database before executing the UPDATE statement. This (assuming all data in the database is valid) would also reduce the chance of harmful changes.

In the following example, all possible files are updated at once. Alternatively, only those fields that change could be updated when needed. However this would require more code and not necessarily be any more efficient. Also, if the user ID is not validated as existing in the database beforehand, the SQL statement will automatically not update the fields if userid is not in the database.

$userid = clean_input($user['userid']);

$sql ="UPDATE Users SET(datestamp='" . $user['datestamp'] . "',attempts='";

$sql .=$user['attempts'] . "',lastattempt='" . $user['lastattempt'] . "',";

$sql .="validattempt='" . $user['validattempt'] . "') WHERE userid='" . $userid . "';";

Notice that the UPDATE code does not include the password field in the WHERE statement. In this example, some fields would be updated when the password is not valid, and some would be updated when the password is valid. If the SQL statement is broken into multiple statements (at least one for valid user ID/password and one for not valid user/password), then the WHERE statement for the valid information can include both the user ID and password, and the statement for non-valid information can just include the password. An example of a complete login, registration, and password-change application using a MySQL database is included on the book’s web site under Chapter 7.

Change Password

The process to change a password will require verification of the current password and then saving the new password. It will also require updating the date contained in the datestamp tag from the XML file. The program code is very similar to the login program.

$userid = $_POST['username'];

$npassword = $_POST['password'];

$newpassword = password_hash($npassword, PASSWORD_DEFAULT);

$password = $_POST['oldpassword'];

$datestamp = date('Y-m-d', strtotime('+30 days'));

$I = 0;

$passed = FALSE;

// First a few properties are set for the userid, new userid, and the new datestamp.

$hash = $user['password'];

if(password_verify($password,$hash))

{

$passed = TRUE;

        $valid_useridpasswords['user'][$I]['password'] = $newpassword;

        $valid_useridpasswords['user'][$I]['datestamp'] = $datestamp;

        $valid_useridpasswords['user'][$I]['attempts'] = 0;

        saveupfile($dog_data_xml,$valid_useridpasswords); // save changes before header call

        $login_string = date('mdYhis') . " | Password Changed | " . $userid . "\n";

        error_log($login_string,3,$user_log_file);

        header("Location: e7logina.php");

}

else

{

$valid_useridpasswords['user'][$I]['lastattempt'] = date('mdYhis'); // last attempted login

}

If the user ID and old password are authenticated correctly then the new password, datestamp, and attempts properties are changed and updated in the XML file. Also an entry is placed in the log file. If the password is not verified, an "invalid userid/password" message is displayed.

Example 7-5. The changepassword.php file

<?php

session_start();

$user_log_file = "user.log";

function saveupfile($dog_data_xml,$valid_useridpasswords)

{

$xmlstring = '<?xml version="1.0" encoding="UTF-8"?>';

    $xmlstring .= "\n<users>\n";

         foreach($valid_useridpasswords as $users)

        {

        foreach($users as $user)

        {

        $xmlstring .="<user>\n<userid>" . $user['userid'] . "</userid>\n";

        $xmlstring .="<password>" . $user['password'] . "</password>\n";

        $xmlstring .="<datestamp>" . $user['datestamp'] . "</datestamp>\n";

        $xmlstring .= "<attempts>" . $user['attempts'] . "</attempts>\n";

        $xmlstring .= "<lastattempt>" . $user['lastattempt'] . "</lastattempt>\n";

        $xmlstring .= "<validattempt>" . $user['validattempt'] . "</validattempt>\n</user>\n";

                }

    }

        $xmlstring .= "</users>\n";

new_valid_data_file = preg_replace('/[0-9]+/', '', $dog_data_xml);

// remove the previous date and time if it exists

$oldxmldata = date('mdYhis') . $new_valid_data_file;

if (!rename($dog_data_xml, $oldxmldata))        {

           throw new Exception("Backup file $oldxmldata could not be created.");        }

file_put_contents($new_valid_data_file,$xmlstring); }

function retrieve_useridpasswordfile() {

$xmlDoc = new DOMDocument();

        if ( file_exists("e7dog_applications.xml") )

        {

        $xmlDoc->load( 'e7dog_applications.xml' );

        $searchNode = $xmlDoc->getElementsByTagName( "type" );

                foreach( $searchNode as $searchNode )

                {

                        $valueID = $searchNode->getAttribute('ID');

                        if($valueID == "UIDPASS")

                        {

                        $xmlLocation = $searchNode->getElementsByTagName( "location" );

                        $dog_data_xml = $xmlLocation->item(0)->nodeValue;

                        break;

                        }

                }

        }

else         {

                throw new Exception("Dog applications xml file missing or corrupt");

        }

        return $dog_data_xml;

}

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

        // valid userid and password but password expired

        echo $_SESSION['message'];

}

try {

if((isset($_POST['username'])) && (isset($_POST['oldpassword'])) && (isset($_POST['password'])) && (isset($_POST['password_confirm'])))

{

        libxml:use_internal_errors(true);

        $dog_data_xml = retrieve_useridpasswordfile();

        $xmlfile = file_get_contents($dog_data_xml);

        $xmlstring = simplexml:load_string($xmlfile);

if ($xmlstring === false) {

        $errorString = "Failed loading XML: ";

        foreach(libxml:get_errors() as $error) {

        $errorString .= $error->message . " " ;  }

        throw new Exception($errorString); }

        $json = json_encode($xmlstring);

        $valid_useridpasswords = json_decode($json,TRUE);

        $userid = $_POST['username'];

        $npassword = $_POST['password'];

        $newpassword = password_hash($npassword, PASSWORD_DEFAULT);

        $password = $_POST['oldpassword'];

        $datestamp = date('Y-m-d', strtotime('+30 days'));

        $I = 0;

$I = 0;

$passed = FALSE;

    foreach($valid_useridpasswords as $users)        {

        foreach($users as $user)        {

            if (in_array($userid, $user))                {

                $hash = $user['password'];

                if(password_verify($password,$hash))

                {

                        $passed = TRUE;

                        $valid_useridpasswords['user'][$I]['password'] = $newpassword;

                        $valid_useridpasswords['user'][$I]['datestamp'] = $datestamp;

                        $valid_useridpasswords['user'][$I]['attempts'] = 0;

                        saveupfile($dog_data_xml,$valid_useridpasswords);

                        // save changes before header call

                        $login_string = date('mdYhis') . " | Password Changed | " . $userid . "\n";

                        error_log($login_string,3,$user_log_file);

                        header("Location: e7login.php");

                        } }

        $I++;

        } }

                // drops to here if not valid password/userid or too many attempts

                if (!$passed){

                        echo "Invalid Userid/Password";

                } } }

   catch(Exception $e) {

        echo $e->getMessage();

   }

?>

<form method="post" action="">

Userid must contain eight or more characters.<br/>

Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters.<br />

Username: <input type="text" pattern=".{8,}" title="Userid must contain eight or more characters." name="username" id="username" required/><br />

Old Password: <input type="password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters." name="oldpassword" id="oldpassword" required /><br />

New Password: <input type="password" pattern="(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,}" title="Password must contain at least one number, one uppercase and lowercase letter, and at least 8 total characters." name="password" id="password" required /><br />

Confirm Password:<input name="password_confirm" required="required" type="password" id="password_confirm" oninput="check(this)"  />

<script language='javascript' type='text/javascript'>

function check(input) {

if (input.value != document.getElementById('password').value) {

    input.setCustomValidity('Password Must be Matching.');

} else {

    // input is valid -- reset the error message

    input.setCustomValidity('');

}

}

</script>

<input type="submit" value="submit">

</form>

In addition to the PHP already explained, Example 7-5 also includes JavaScript code to verify the correct format of the new password and the correct entry of the new password twice. A verification of the proper user ID and password entries in the PHP code has not been included in the example. Since the information is traveling over the Internet from the form to the PHP program, the user ID and password should also be verified in the PHP program. That coding is left as an exercise for this section.

JSON Data

The only changes needed for the change password program to work with JSON data are indicated in the previous JSON sections of this chapter.

MySQL Data

The UPDATE SQL statement needs to “set” the password property in the same structure as the other fields that were changed in the previous MySQL section. A complete authentication program using MySQL is included under Chapter 7 on the book’s web site.

Do It

1.

Copy the example files for this section from the book’s web site. Adjust the change password program to include PHP code that verifies the correct format of the user ID, password, and new password.

2.

Copy the example files for this chapter from the book’s web site. Adjust the registration program to check for a duplicate user ID before attempting to insert a new user ID/password combination. Hint: This can be done using the in_array method. If the user ID exists, display a message back to the users.

3.

Copy the example files for this section from the book’s web site. Adjust the programs necessary to require the users to also enter their name, phone, and e-mail when registering. Also make sure to change the other programs to keep this information valid.

Chapter Terms

Authentication

session

session_start

isset

Session properties

$_SESSION

Verification code

.{8,}

?=.*\d

?=.*[A-Z]

?=.*[a-z]

header

array_keys

password_hash

password_verify

non-registered users

$_SERVER['REMOTE_ADDR']

registration page

preg_match

strlen

file_get_contents

preg_quote

preg_replace

expire passwords

Password sniffer programs

limited number of attempts

Timeout period

strtotime

SQL Injection

 

Chapter Questions and Projects

Multiple Choice

1.

Authentication does which of the following?

a.

Provides security for an application.

b.

Verifies user IDs and passwords.

c.

Should use encrypted passwords.

d.

All of the above.

2.

Sessions do which of the following?

a.

Are created using the session_create method

b.

Allow the sharing of information between programs

c.

Don’t provide any security benefits

d.

All of the above

3.

Registration pages do which of the following?

a.

Allow the users to create their own user IDs and passwords

b.

Can be used to gather information about the users

c.

Should encrypt the password before storing it

d.

All of the above

4.

Verification code does which of the following?

a.

Uses session_start to attach the program to a current session

b.

Verifies that a user has logged in

c.

Must be attached only to programs in the interface tier

d.

All of the above

5.

Which of these describes SQL injection?

a.

The process of using an SQL statement to update a database

b.

The process of inserting variables in a SQL statement for flexibility

c.

Causes data to be corrupted

d.

All of these

True/False

1.

MD5 is the most up-to-date and secure encryption technique.

2.

password_hash should be used to create an encrypted password.

3.

Users should be notified that they have exceeded the maximum number of attempts to enter a correct user ID and password.

4.

An authentication system does not need to timeout passwords and force its users to change their passwords when they time out.

5.

preg_replace can be used with an encrypted password to ensure that PHP does not interpret special characters as PHP commands.

Short Answer/Essay

1.

Explain the techniques that can be used to reduce the chances that a password sniffing program can discover the correct user ID and password combination.

2.

Explain how sessions work. Include an explanation on how they can help secure an application with user ID and password authentication.

3.

What is SQL injection? How can it be avoided?

4.

Why should passwords be encrypted? Explore the Internet and discover the latest versions of encryption. Does the most current version of PHP use the newest version of encryption?

Projects

1.

Download log maintenance files from Chapter 6 or use your own maintenance files that you created from Chapter 6. Use the techniques shown in this chapter to secure these files with user ID and password authentication.

2.

Download the files from this chapter. Update the files and programs so users can request their passwords. A temporary password (new field in the XML file) must randomly be created (use rand) and e-mailed to the users. The password should have a quick expiration (one day or less). The user must be able to verify other information entered via the registration page (security question, or other personal info) to request the password. If the user signs in correctly with the temporary password, the system should make the user change the password.

Term Project

1.

Update the ABC Computer Parts Inventory application to include user ID and password authentication as shown in this chapter. Be sure to secure any log maintenance programs related to the application.