Friday, May 7, 2010

1:19 AM

In some cases you may want your loging form to be able to prevent automatic login by a robot ( script ). To achieve this we can create a login form which displays an image showing random numbers. The login form will have an extra input field to enter the values shown.

Before working on the login form we must take care of the script that create the verification image first. Here is the code :

<?php
session_start();

// generate 5 digit random number
$rand = rand(10000, 99999);


// create the hash for the random number and put it in the session
$_SESSION['image_random_value'] = md5($rand);


// create the image
$image = imagecreate(60, 30);


// use white as the background image
$bgColor = imagecolorallocate ($image, 255, 255, 255);


// the text color is black
$textColor = imagecolorallocate ($image, 0, 0, 0);


// write the random number
imagestring ($image, 5, 5, 8, $rand, $textColor); 

// send several headers to make sure the image is not cached 
// taken directly from the PHP Manual

// Date in the past 
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");


// always modified 
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");


// HTTP/1.1 
header("Cache-Control: no-store, no-cache, must-revalidate"); 
header("Cache-Control: post-check=0, pre-check=0", false);


// HTTP/1.0 
header("Pragma: no-cache");


// send the content type header so the image is displayed properly
header('Content-type: image/jpeg');


// send the image to the browser
imagejpeg($image);


// destroy the image to free up the memory
imagedestroy($image);
?>

To create a five digit random number we use rand() function and specify that the random number must be between 10000 and 99999. We put the hash value of this random number in the session. This hash value will be used by the login script to check if the entered number is correct.

Next we create a small image, 60 x 30 pixels, using imagecreate(). We set the background color to white ( RGB = 255, 255, 255 ) using imagecolorallocate() function. Note that the first call to imagecolorallocate() will always set the background color for the image. Then we set the text color as black ( RGB = 0, 0, 0 ). Feel free to change the color text to your liking.

To print the random number to the image we use the function imagestring(). In the script above we call this function like this : imagestring ($image, 5, 5, 8, $rand, $textColor);

The first argument passed to this function is the image handler ( $image ). The second one ( 5 ) is the font. You can choose from one to five where one is the smallest font. The third and fourth parameter is the horizontal and vertical coordinate where we will print the image. The top left corner is defined as 0, 0. The last one is the text color which is black as mentioned earlier.

After we got the image ready we can now send it to the browser. But before doing that we must set several headers to make sure that the image is not cached. If the image is cached then the login form will show the same image even if you refresh it. That will cause a problem since the random number is always different.

Finally after everything is set we send the image to the browser using imagejpeg() and to free the memory we useimagedestroy().

The Login Form

The login form is pretty much the same but only have extra field to enter the displayed number.


<?php
// ... the login script is up here
?>
<html>
<head>
<title>Basic Login</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
</head>


<body>
<?php
if ($errorMessage != '') {
?>
<p align="center"><strong><font color="#990000"><?php echo $errorMessage; ?></font></strong></p>
<?php
}
?>
<form action="" method="post" name="frmLogin" id="frmLogin">
<table width="500" border="1" align="center" cellpadding="2" cellspacing="2">
<tr>
<td width="150">User Id</td>
<td><input name="txtUserId" type="text" id="txtUserId"></td>
</tr>
<tr>
<td width="150">Password</td>
<td><input name="txtPassword" type="password" id="txtPassword"></td>
</tr>
<tr>
<td width="150">Enter Number</td>
<td><input name="txtNumber" type="text" id="txtNumber" value="">
&nbsp;&nbsp;<img src="randomImage.php"></td>
</tr>


<tr>
<td width="150">&nbsp;</td>
<td><input name="btnLogin" type="submit" id="btnLogin" value="Login"></td>
</tr>
</table>
</form>
</body>
</html>

To check if the login information is correct we first check if the entered number is the same one as displayed in the image. To do this we check the hash of the entered number and see if it match the one saved in the session. If the number don't match we just set an error message.

If the number do match we continue checking the given user id and password just like the previous example. If the userid and password combination is correct we set $_SESSION['image_is_logged_in'] to true and move on to the main page


<?php
// we must never forget to start the session
session_start();


$errorMessage = '';
if (isset($_POST['txtUserId']) && isset($_POST['txtPassword'])) {
   // first check if the number submitted is correct
   $number = $_POST['txtNumber'];

   if (md5($number) == $_SESSION['image_random_value']) {
      include 'library/config.php';
      include 'library/opendb.php';

      $userId = $_POST['txtUserId'];
      $password = $_POST['txtPassword'];


      // check if the user id and password combination exist
      $sql = "SELECT user_id 
              FROM tbl_auth_user
              WHERE user_id = '$userId' 
                    AND user_password = PASSWORD('$password')";

      $result = mysql_query($sql) or 
                die('Query failed. ' . mysql_error()); 

      if (mysql_num_rows($result) == 1) {
         // the user id and password match, 
         // set the session
         $_SESSION['image_is_logged_in'] = true;


         // remove the random value from session 
         $_SESSION['image_random_value'] = '';

         // after login we move to the main page
         header('Location: main.php');
         exit;
      } else {
         $errorMessage = 'Sorry, wrong user id / password';
      }

      include 'library/closedb.php';
   } else {
      $errorMessage = 'Sorry, wrong number. Please try again';
   } 
}
?>

We don't need to discuss about main.php and logout.php since they are the same as previous example except the session name is now called $_SESSION['image_is_logged_in']. So instead of working on those two files let's move on to a more interesting stuff...

Improving The Verification Image

We can improve the verification image in at least two ways. They are :
  1. Using alphanumeric characters as the verification code instead of numbers
  2. Using backgound images
For the first improvement the only thing we need to change is the way we generate the code. Take a look at the code below


<?php
session_start();

$alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";


// generate the verication code 
$rand = substr(str_shuffle($alphanum), 0, 5);


// ... no changes after this point
?>

We start by defining the characters that we want to use in the verification code. For this example we use upper case alphabet plus numbers. The code is generated using the combination of str_shuffle() and substr() function. Using str_shuffle() we jumble all the characters in $aplhanum and then using substr() we take just the first five characters. The result will look something like "D79ZG".

The second improvement is by using background images. Maybe you already know this but there are software/scripts that can extract the characters displayed as images. And if the verification image only use plain background color identifying the characters wil be quite easy.

For this reason we will make the verification code displayed on a background image. In this tutorial we will only use four different background images. You can add as many background images as you want in your own code. Here are the background images :
  • background image #1 : 
  • background image #2 : 
  • background image #3 : 
  • background image #4 : 
Note : When you want to create a background image make sure the code will still be readable on them. For instance it's quite hard ( at least for me ) to recognize the code when the code is displayed on image #1 and image #4.  

Here is the code for this second improvement


<?php
session_start();

$alphanum = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";


// generate the verication code 
$rand = substr(str_shuffle($alphanum), 0, 5);


// choose one of four background images
$bgNum = rand(1, 4);

$image = imagecreatefromjpeg("background$bgNum.jpg");

$textColor = imagecolorallocate ($image, 0, 0, 0);

// write the code on the background image
imagestring ($image, 5, 5, 8, $rand, $textColor);


// ... no changes after this point
?>

After making the verification code we randomly pick one background image. Then we create an image object from the chosen background using imagecreatefromjpeg() and draw the code on the background. The rest of the code is the same as randomImage1.php and randomImage2.php so no need to explain it here.

0 comments: