Article

The PHP Anthology Volume 2, Chapter 1 - Access Control

Page: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Next

MD5 Digests

MD5 is a simple message digest algorithm (often referred to as one-way encryption) that translates any string (such as a password) into a short series of ASCII characters, called an MD5 digest. A particular string will always produce the same digest, but it is practically impossible to guess a string that will produce a given digest. By storing only the MD5 digest of your users' passwords in the database, you can verify their login credentials without actually storing the passwords on your server! The built-in PHP function md5 lets you calculate the MD5 digest of any string in PHP.

The constructor goes on to create a new instance of the Session class, which it stores in an instance variable, and finally calls the login method to validate the user against the database.

Here's the login method:

Example 1.7. AccessControl/Auth.php (in SPLIB) (excerpt)    
   
 /**    
  * Checks username and password against database    
  * @return void    
  * @access private    
  */    
 function login()    
 {    
   // See if we have values already stored in the session    
   if ($this->session->get('login_hash')) {    
     $this->confirmAuth();    
     return;    
   }    
   
   // If this is a fresh login, check $_POST variables    
   if (!isset($_POST[USER_LOGIN_VAR]) ||    
       !isset($_POST[USER_PASSW_VAR])) {    
     $this->redirect();    
   }    
   
   if ($this->md5) {    
     $password = md5($_POST[USER_PASSW_VAR]);    
   } else {    
     $password = $_POST[USER_PASSW_VAR];    
   }    
   
   // Escape the variables for the query    
   $login = mysql_escape_string($_POST[USER_LOGIN_VAR]);    
   $password = mysql_escape_string($password);    
   
   // Query to count number of users with this combination    
   $sql = "SELECT COUNT(*) AS num_users    
           FROM " . USER_TABLE . "    
           WHERE    
             " . USER_TABLE_LOGIN . "='$login' AND    
             " . USER_TABLE_PASSW . "='$password'";    
   
   $result = $this->db->query($sql);    
   $row = $result->fetch();    
   
   // If there isn't is exactly one entry, redirect    
   if ($row['num_users'] != 1) {    
     $this->redirect();    
   // Else is a valid user; set the session variables    
   } else {    
     $this->storeAuth($login, $password);    
   }    
 }

The login method first checks to see whether values for the user name and password are currently stored in the session; if they are, it calls the confirmAuth method (see below). If user name and password values are not stored in the session, the method checks to see if they're available in the $_POST array and, if they're not, it calls the redirect method (see below).

Assuming it has found the $_POST values, the script performs a query against the database to see if it can find a record to match the submitted user name and password. There must be exactly one matching record, otherwise the visitor will be redirected. Finally, assuming the script has got this far, it registers the user name and password as session variables using the storeAuth method (see below), which makes them available for future page requests.

One thing to note about the login method is that it assumes magic_quotes_gpc is switched off, as it uses mysql_escape_string to prepare submitted values for incorporation into database queries. In the scripts that utilize this class, we'll include the script that nullifies the effect of magic quotes (see the section called "How do I write portable PHP code?").

Let's now look at the methods that login uses.

Example 1.8. AccessControl/Auth.php (in SPLIB) (excerpt)    
   
 /**    
  * Sets the session variables after a successful login    
  * @return void    
  * @access protected    
  */    
 function storeAuth($login, $password)    
 {    
   $this->session->set(USER_LOGIN_VAR, $login);    
   $this->session->set(USER_PASSW_VAR, $password);    
   
   // Create a session variable to use to confirm sessions    
   $hashKey = md5($this->hashKey . $login . $password);    
   $this->session->set('login_hash', $hashKey);    
 }

The storeAuth method is used to add the user name and password to the session, along with a hash value. This is comprised of a seed value defined using the Auth class (remember the $hashKey parameter required by the constructor?), as well as the user name and password values. As we'll see in the confirmAuth method below, instead of laboriously checking the database to verify the login credentials whenever a user requests a page, the class simply checks that the current user name and password produce a hash value that's the same as that stored in the session. This prevents potential attackers from attempting to change the stored user name after login if your PHP configuration has register_globals enabled.

As I've just described, the confirmAuth method is used to double check credentials stored in the session once a user is logged in. Notice how we reproduce the hash built by the storeAuth method. If this fails to match the original hash value, the user is immediately logged out.

Example 1.9. AccessControl/Auth.php (in SPLIB) (excerpt)    
   
 /**    
  * Confirms that an existing login is still valid    
  * @return void    
  * @access private    
  */    
 function confirmAuth()    
 {    
   $login = $this->session->get(USER_LOGIN_VAR);    
   $password = $this->session->get(USER_PASSW_VAR);    
   $hashKey = $this->session->get('login_hash');    
   if (md5($this->hashKey . $login . $password) != $hashKey)    
   {    
     $this->logout(true);    
   }    
 }

The logout method is the only public method in the Auth class. It's used to remove the login credentials from the session and return the user to the login form:

Example 1.10. AccessControl/Auth.php (in SPLIB) (excerpt)    
   
 /**    
  * Logs the user out    
  * @param boolean Parameter to pass on to Auth::redirect()    
  *               (optional)    
  * @return void    
  * @access public    
  */    
 function logout($from = false)    
 {    
   $this->session->del(USER_LOGIN_VAR);    
   $this->session->del(USER_PASSW_VAR);    
   $this->session->del('login_hash');    
   $this->redirect($from);    
 }

The redirect method is used to return the visitor to the login form (or whatever URL we specified upon instantiating the Auth class):

Example 1.11. AccessControl/Auth.php (in SPLIB) (excerpt)    
   
 /**    
  * Redirects browser and terminates script execution    
  * @param boolean adverstise URL where this user came from    
  *               (optional)    
  * @return void    
  * @access private    
  */    
 function redirect($from = true)    
 {    
   if ($from) {    
     header('Location: ' . $this->redirect . '?from=' .    
            $_SERVER['REQUEST_URI']);    
   } else {    
     header('Location: ' . $this->redirect);    
   }    
   exit();    
 }

Unless you tell it not to, this method will send the from variable via the query string to the script to which the browser is redirected. This allows the login form to return the users to the location from which they came; it saves the users from having to navigate back to that point, which might be useful if, for example, a session times out. Note that in the logout method we specified that redirect should not provide the from variable. If it did, the script might return users to the URL they used to log out, putting them in a loop from which they couldn't log in.

One important note to make here is that the redirection URL (which is set by the constructor) should be absolute, not relative. According to the HTTP specification, an absolute URL must be provided when a Location header is used. Later on, when we put this class into action, I'm going to break that rule and use a relative URL, because I can't guess the script's location on your server. This works because most recent browsers understand it (even though they shouldn't). On a live site, make sure you provide a full, absolute URL.

Finally, and most importantly, we use exit to terminate all further processing. This prevents the calling script sending the protected content that follows the authentication code. Although we've sent a header that should redirect the browser, we can't rely on the browser to do what it's told. If the request were sent by, for instance, a Perl script pretending to be a Web browser, whoever was using the script would, no doubt, have total control over its behavior and could quite easily ignore the instruction to redirect elsewhere. Hence, the exit statement is critical.

Overall, this approach helps save us from our own mistakes; if a given user is not valid, script execution halts and the user is redirected to another "safe" page. The alternative approach might be to build conditional statements into a page, like this:

if ($auth->login()) {    
 echo 'You are logged in';    
} else {    
 echo 'Invalid login';    
}

However, this isn't really a good idea. In a more complex scenario, which involves multiple file inclusions, and has classes take responsibility for different parts of the application, it's possible that you may unwittingly allow unauthorized visitors access. The approach of redirection is simple, reliable, and less likely to lead to such nasty surprises.

If you liked this article, share the love:
Print-Friendly Version Suggest an Article

Sponsored Links