Captcha (keeping those spammers out of your “business”) Part 03: Adding a user interface to the captcha

December 21, 2007

In Part 03 of this series of posts we are going to build a simple input form to hold the captcha image.

First, I will write out some HTML and in the <body> I will include a simple form.

<form method="post" action="" id="comment_form">
  <ul>
    <li>
      <label for="form_name">Name</label>
      <input type="text" name="form_name" id="form_name" />
    </li>
    <li>
      <label for="form_email">Email</label>
      <input type="text" name="form_email" id="form_email" />
    </li>
    <li>
      <label for="form_captcha"> </label>
      <img id="form_captcha" />
    </li>
    <li>
      <label for="form_captcha_instructions"> </label>
      <span id="form_captcha_instructions">enter the code exactly as shown above</span>
    </li>
    <li>
      <label for="form_captcha_value">Code<em>*</em></label>
      <input type="text" name="form_captcha_value" id="form_captcha_value" />
    </li>
    <li>
      <label for="form_comment">Comment<em>*</em></label>
      <textarea id="form_comment" name="form_comment" rows="10"></textarea>
    </li>
    <li>
      <label for="submit"> </label>
      <input type="button" name="submit" value="Add Comment" id="submit"/>
    </li>
  </ul>
</form>

Curious to see how horrible this looks? Below is a snapshot:

Captcha Form, No Style — Literally

Before we continue with the Javascript, I will add some CSS to cleanup the layout:

      *
      {
        font: 10pt Arial;
        margin: 0;
        padding: 0;
      }
      #comment_form
      {
        margin: 5px;
        padding: 5px;
        border: 1px Solid #e5e5e5;
        width: 400px;
        background: #f9f9f9;
      }
        #comment_form ul
        {
          list-style: none;
        }
        #comment_form ul li
        {
          margin-bottom: 5px;
        }
            #comment_form ul li label
            {
              width: 75px;
              float: left;
            }
            #comment_form ul li textarea
            {
              border: 1px Solid #e5e5e5;
              width: 313px;
            }
            #comment_form ul li input
            {
              border: 1px Solid #e5e5e5;
              width: 313px;
            }
              #comment_form ul li label em
              {
                color: red;
              }

There, some simple CSS and things are starting to look a lot better.

Captcha Form, with some style

To include the captcha image within the form I will use some javascript to call the captcha.php. This is easily accomplished by using some very simple Javascript to modify the DOM.

index.php cont…

      function getCaptchaImage(session_id)
      {
        document.getElementById("form_captcha").src="captcha.php?action=challenge&session_id=" + session_id;
      }

The getCaptchaImage() function adds the ’src’ attribute to the <img> in our HTML. We can call this function in the <body> by using <body onLoad=”getCaptchaImage(session_id);”>. But the session_id value will need to be determined by some PHP.

Up to this point, here is the index.php:

index.php

<?php
session_start();
?>
<html>
  <head>
    <style type="text/css">
      *
      {
        font: 10pt Arial;
        margin: 0;
        padding: 0;
      }
      #comment_form
      {
        margin: 5px;
        padding: 5px;
        border: 1px Solid #e5e5e5;
        width: 400px;
        background: #f9f9f9;
      }
        #comment_form ul
        {
          list-style: none;
        }
        #comment_form ul li
        {
          margin-bottom: 5px;
        }
            #comment_form ul li label
            {
              width: 75px;
              float: left;
            }
            #comment_form ul li textarea
            {
              border: 1px Solid #e5e5e5;
              width: 313px;
            }
            #comment_form ul li input
            {
              border: 1px Solid #e5e5e5;
              width: 313px;
            }
              #comment_form ul li label em
              {
                color: red;
              }
    </style>
    <script language="Javascript">
      function getCaptchaImage(session_id)
      {
        document.getElementById("form_captcha").src="captcha.php?action=challenge&session_id=" + session_id;
      }
    </script>
  </head>
<body onLoad="getCaptchaImage('<?php echo session_id();?>');">
<form method="post" action="" id="comment_form">
  <ul>
    <li>
      <label for="form_name">Name</label>
      <input type="text" name="form_name" id="form_name" />
    </li>
    <li>
      <label for="form_email">Email</label>
      <input type="text" name="form_email" id="form_email" />
    </li>
    <li>
      <label for="form_captcha"> </label>
      <img id="form_captcha" />
    </li>
    <li>
      <label for="form_captcha_instructions"> </label>
      <span id="form_captcha_instructions">enter the code exactly as shown above</span>
    </li>
    <li>
      <label for="form_captcha_value">Code<em>*</em></label>
      <input type="text" name="form_captcha_value" id="form_captcha_value" />
    </li>
    <li>
      <label for="form_comment">Comment<em>*</em></label>
      <textarea id="form_comment" name="form_comment" rows="10"></textarea>
    </li>
    <li>
      <label for="submit"> </label>
      <input type="button" name="submit" value="Add Comment" id="submit" />
    </li>
  </ul>
</form>
</body>
</html>

And our result:

Captcha Form, dynamically loading the captcha

A quick check of our database verifies that the correct captcha values have been saved to the database (our PHP is working!) :

mysql> select * from captcha;
+----------------------------+-----------+
| sid                        | challenge |
+----------------------------+-----------+
| upgf36fgm8o9b1a381fmk90gf4 | 406fb     |
+----------------------------+-----------+
1 row in set (0.00 sec)

Now we need a way to determine if the user has entered the correct captcha code to the input box. The check will be completed when the user clicks the “Add Comment” button. By using some Ajax we can easily determine if the code is correct.

index.php cont…

      function createRequestObject()
      {
        var reqObj = null;
        //Attempt to create native XMLHttpRequest Object
        if(window.XMLHttpRequest)
        {
          try
          {
            reqObj = new XMLHttpRequest();
          }
          catch(error)
          {
            reqObj = null;
          }
        }
        //Attempt to create ActiveX version
        else if(window.ActiveXObject)
        {
          try
          {
            reqObj = new ActiveXObject("Msxml2.XMLHTTP");
          }
          catch(error)
          {
            try
            {
              reqObj = new ActiveXObject("Microsoft.XMLHTTP");
            }
            catch(error)
            {
              reqObj = null;
            }
          }
        }
        return reqObj;
      }

      var ro = createRequestObject();

      function testChallenge(sid)
      {
        var challenge = document.getElementById('form_captcha_value').value;
        ro.open('get', 'captcha.php?session_id=' + sid + '&action=verify&challenge=' + challenge, true);
        ro.onreadystatechange = getChallengeResponse;
        ro.send(null);
      }

      function getChallengeResponse()
      {
        if(ro.readyState == 4)
        {
          alert(ro.responseText);
        }
      }

In the <script> I have added 3 new functions and 1 variable. The createRequestObject() function is responsible for creating our repsonse object based on the user’s browser type (IE or a browser that natively support XMLHttpRequest). The testChallenge() function sends the request to our captcha.php and waits for a response. The getChallengeResponse() will display an alert with the response from our captcha.php when it has completed processing on the server.

Now to trigger the testChallenge() function we will modify the “Add Comment” button.

index.php complete

<?php
session_start();
?>
<html>
  <head>
    <style type="text/css">
      *
      {
        font: 10pt Arial;
        margin: 0;
        padding: 0;
      }
      #comment_form
      {
        margin: 5px;
        padding: 5px;
        border: 1px Solid #e5e5e5;
        width: 400px;
        background: #f9f9f9;
      }
        #comment_form ul
        {
          list-style: none;
        }
        #comment_form ul li
        {
          margin-bottom: 5px;
        }
            #comment_form ul li label
            {
              width: 75px;
              float: left;
            }
            #comment_form ul li textarea
            {
              border: 1px Solid #e5e5e5;
              width: 313px;
            }
            #comment_form ul li input
            {
              border: 1px Solid #e5e5e5;
              width: 313px;
            }
              #comment_form ul li label em
              {
                color: red;
              }
    </style>
    <script language="Javascript">
      function createRequestObject()
      {
        var reqObj = null;
        //Attempt to create native XMLHttpRequest Object
        if(window.XMLHttpRequest)
        {
          try
          {
            reqObj = new XMLHttpRequest();
          }
          catch(error)
          {
            reqObj = null;
          }
        }
        //Attempt to create ActiveX version
        else if(window.ActiveXObject)
        {
          try
          {
            reqObj = new ActiveXObject("Msxml2.XMLHTTP");
          }
          catch(error)
          {
            try
            {
              reqObj = new ActiveXObject("Microsoft.XMLHTTP");
            }
            catch(error)
            {
              reqObj = null;
            }
          }
        }
        return reqObj;
      }

      var ro = createRequestObject();

      function testChallenge(sid)
      {
        var challenge = document.getElementById('form_captcha_value').value;
        ro.open('get', 'captcha.php?session_id=' + sid + '&action=verify&challenge=' + challenge, true);
        ro.onreadystatechange = getChallengeResponse;
        ro.send(null);
      }

      function getChallengeResponse()
      {
        if(ro.readyState == 4)
        {
          alert(ro.responseText);
        }
      }  

      function getCaptchaImage(session_id)
      {
        document.getElementById("form_captcha").src="captcha.php?action=challenge&session_id=" + session_id;
      }
    </script>
  </head>
  <body onLoad="getCaptchaImage('<?php echo session_id();?>');">
  <form method="post" action="" id="comment_form">
    <ul>
      <li>
        <label for="form_name">Name</label>
        <input type="text" name="form_name" id="form_name" />
      </li>
      <li>
        <label for="form_email">Email</label>
        <input type="text" name="form_email" id="form_email" />
      </li>
      <li>
        <label for="form_captcha"> </label>
        <img id="form_captcha" />
      </li>
      <li>
        <label for="form_captcha_instructions"> </label>
        <span id="form_captcha_instructions">enter the code exactly as shown above</span>
      </li>
      <li>
        <label for="form_captcha_value">Code<em>*</em></label>
        <input type="text" name="form_captcha_value" id="form_captcha_value" />
      </li>
      <li>
        <label for="form_comment">Comment<em>*</em></label>
        <textarea id="form_comment" name="form_comment" rows="10"></textarea>
      </li>
      <li>
        <label for="submit"> </label>
        <input type="button" name="submit" value="Add Comment" id="submit" onClick="testChallenge('<?php echo session_id(); ?>');"/>
      </li>
    </ul>
  </form>
  </body>
</html>

Now lets refresh our index.php and run it from the top.

Step 1: Viewing the form

Captcha Form, Running the Scripts from the top

Step 2: Verify the database has the correct challenge code

mysql> select * from captcha;
+----------------------------+-----------+
| sid                        | challenge |
+----------------------------+-----------+
| upgf36fgm8o9b1a381fmk90gf4 | 8d2ac     |
+----------------------------+-----------+
1 row in set (0.00 sec)

Step 3: Adding the correct captcha challenge code into the input box and the result after clicking “Add Comment”

Captcha Form, Running the Scripts from the top 2

Step 4: Adding the incorrect captcha challenge code into the input box and the result after clicking “Add Comment”

Captcha Form, Running the Scripts from the top 3

Everything is working just the way it was expected too. Now that we have the fundamentals of building a simple captcha method there are some additional issues that require future planning:

  1. Accessibility is a large concern with this captcha as it currently sits. Users with visual impairments may not be able to use this method to access your site. How can we add to our script to ensure all potential users are able to utilize our sites?
  2. I would not consider this captcha to be all that strong. More advanced captcha methods would utilize more sophisticated methods to prevent OCR bots from breaking our spam prevention.

This series of posts was not intended to be an absolute guide to developing captcha. The purpose of this post was to demonstrate one possible method to build a captcha from the ground up in 1 hour or less.

Please feel free to provide feedback or request clarification, but remember “I am NOT a web designer!”


Captcha (keeping those spammers out of your “business”) Part 02: Extending the PHP the add the challenge code to a database

December 21, 2007

In Part 02 of this series of posts I am going to extend the existing PHP script that generates the captcha image to include functions that add the challenge code to a database, a function to verify a user’s challenge response and some main logic to determine which action to take.

To store the captcha challenge code and the current user’s session id, I have created a simple database “portfolio” and in that database is the “captcha” table (as seen below):

mysql> show columns from captcha;
+-----------+-------------+------+-----+---------+-------+
| Field     | Type        | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| sid       | varchar(45) | NO   | PRI |         |       |
| challenge | varchar(45) | NO   |     |         |       |
+-----------+-------------+------+-----+---------+-------+
2 rows in set (0.05 sec)

Now let us add a function to add the challenge code to the database:

captcha.php cont…

function update_database($challenge, $sid)
{
  $connection = mysql_connect("localhost", "root", "root");
  $db = mysql_select_db("portfolio");
  $deletesql = "  DELETE
                  FROM    captcha
                  WHERE   sid = '". $sid ."';
                ";
  $updatesql = "  INSERT INTO captcha
                  (sid, challenge)
                  VALUES('". $sid. "','". $challenge ."');
               ";
  mysql_query($deletesql);
  mysql_query($updatesql);
  mysql_close($connection);
}

The new function update_database() opens a connection to a MySQL database, accesses the desired database and runs two queries. The first query deletes all records in the captcha table where the session id is equal to the $sid variable. The second query adds the current $sid of the user and $challenge code from our generateMD5String() function.

Now we need a simple function to test if a user has entered the correct captcha code. This is accomplished by querying the captcha table for a record that contains their sid, and challenge.

captcha.php cont…

function verify_code()
{
  $connection = mysql_connect("localhost", "root", "root");
  $db = mysql_select_db("portfolio");
  $sql = "SELECT * FROM captcha where sid='". $_GET['session_id'] ."' and challenge='". $_GET['challenge'] ."';";
  $result = mysql_query($sql);
  if(mysql_num_rows($result) == 1)
  {
    echo "Authenticated";
  }
  mysql_close($connection);
}

verify_code() uses $_GET values (values from the querystring) of ’session_id’ and ‘challenge’ to run the query. If the database has a record with the matching session id and challenge, it is safe to assume that the user has passed our automated test.

Before we move to the Javascript and HTML to present a form and handle all the fun ajax, we need some additional logic to drive our captcha.php flow.

captcha.php cont…

switch($_GET['action'])
{
  case "challenge":
    generateCaptcha();
    break;
  case "verify":
    verify_code();
    break;
  default:
    generateCaptcha();
}

With the use of a swtich, we can direct the program flow quite easily. The switch is watching for the ‘action=’ portion of the querystring to determine which function to call.

Putting it all together, we now are able to move to the client side development.

captcha.php complete

<?php

function generateMD5String($chars = 5)
{
  //Generate a random md5 code $chars long
  $md5 = md5(microtime() * mktime());
  $string = substr($md5,0,$chars);
  return $string;
}

function drawRandomLines($im, $recHeight, $recWidth, $lines = 50)
{
  //Add $lines lines to the image with random x, y coordinates
  //within the $recHeight, $recWidth values
  for($i = 1; $i <= $lines; $i++)
  {
    $line = imagecolorallocate($im, 207, 207, 207);
    imageline($im, rand(0, $recHeight), rand(0, $recWidth), rand(0, $recHeight), rand(0, $recWidth), $line);
  }
}

function generateCaptcha($recHeight = 200, $recWidth = 75)
{
  $challenge_string = generateMD5String();    update_database($challenge_string, $_GET['session_id']);

  //Set the header for the PNG
  header("Content-type: image/png");
  $im = imagecreatetruecolor($recHeight, $recWidth);
  //Let's make the font black (0,0,0)
  $font_color = imagecolorallocate($im, 0, 0, 0);
  //For fun I have set the background to a greyish color
  $im_background = imagecolorallocate($im, 229, 229, 229);
  //Fill in the rectangle with color
  imagefilledrectangle($im, 0, 0, $recHeight, $recWidth, $im_background);  

  //Load the gdf to display the challenge word
  $font = imageloadfont("./fonts/alienation48.gdf");

  imagestring($im, $font, rand(0, $recHeight/3), rand(0, $recWidth/3), $challenge_string, $font_color);

  //To add some further complexity, I want to draw lines across the random text
  drawRandomLines($im, $recHeight, $recWidth);

  //Display the image
  imagepng($im);
}   

function update_database($challenge, $sid)
{
  $connection = mysql_connect("localhost", "root", "root");
  $db = mysql_select_db("portfolio");
  $deletesql = "  DELETE
                  FROM    captcha
                  WHERE   sid = '". $sid ."';
                ";
  $updatesql = "  INSERT INTO captcha
                  (sid, challenge)
                  VALUES('". $sid. "','". $challenge ."');
               ";
  mysql_query($deletesql);
  mysql_query($updatesql);
  mysql_close($connection);
}     

function verify_code()
{
  $connection = mysql_connect("localhost", "root", "root");
  $db = mysql_select_db("portfolio");
  $sql = "SELECT * FROM captcha where sid='". $_GET['session_id'] ."' and challenge='". $_GET['challenge'] ."';";
  $result = mysql_query($sql);
  if(mysql_num_rows($result) == 1)
  {
    echo "Authenticated";
  }
  mysql_close($connection);
}      

switch($_GET['action'])
{
  case "challenge":
    generateCaptcha();
    break;
  case "verify":
    verify_code();
    break;
  default:
    generateCaptcha();
}  ?>

Part 03 in this series of posts will focus on using Javascript, Ajax, CSS and PHP to build a form to utilize our captcha.

captcha_003.png