PHP Login Form CAPTCHA Code Example

The PHP Login Captcha code example shows how to add BotDetect CAPTCHA validation to simple PHP login forms.

First Time Here?

Check the BotDetect PHP Captcha Quickstart for key integration steps.

To prevent bots from trying to guess the login info by brute force submission of a large number of common values, the visitor first has to prove they are human (by solving the CAPTCHA), and only then is their username and password submission checked against the authentication data store.

Also, if they enter an invalid username + password combination three times, they have to solve the CAPTCHA again. This prevents attempts in which the attacker would first solve the CAPTCHA themselves, and then let a bot brute-force the authentication info.

To keep the example code simple, the example doesn't access a data store to authenticate the user, but accepts all logins with usernames and passwords at least 5 characters long as valid.

Download the BotDetect PHP CAPTCHA Library and run this example

Downloaded Location

The PHP login form Captcha code example is included in the examples/php-login-form-captcha-example folder of the download package.

index.php

<?php session_start(); ?>
<?php require("botdetect.php"); ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
  <title>BotDetect PHP CAPTCHA Validation: 
    PHP Login Form CAPTCHA Code Example</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <link type="text/css" rel="Stylesheet"
    href="<?php echo CaptchaUrls::LayoutStylesheetUrl() ?>" />
  <link type="text/css" rel="Stylesheet" href="stylesheet.css" />
</head>
<body>
  <form method="post" action="process-login.php" class="column" id="form1">

    <h1>BotDetect PHP CAPTCHA Validation: <br />
       PHP Login Form CAPTCHA Code Example</h1>

    <fieldset>
      <legend>CAPTCHA included in PHP Login form validation</legend>

      <div class="input">
        <label for="Username">Username:</label>
        <input type="text" name="Username" id="Username" class="textbox" 
          value="<?php if (isset($_REQUEST['Username'])) { 
            echo $_REQUEST['Username']; } ?>" />
      </div>
      
      <div class="input">
        <label for="Password">Password:</label>
        <input type="password" name="Password" id="Password" class="textbox" />
      </div>

      <?php // authentication failed, show error message
        $error = '';
        if (isset($_REQUEST['error'])) {
          $error = $_REQUEST['error'];
        }
        if ('Format' == $error) { ?>
          <p class="incorrect">Invalid authentication info</p><?php
        } else if ('Auth' == $error) { ?>
          <p class="incorrect">Authentication failed</p><?php
        }
      ?>

      <div class="input">
      <?php // Adding BotDetect Captcha to the page
        $LoginCaptcha = new Captcha('LoginCaptcha');
        $LoginCaptcha->UserInputID = 'CaptchaCode';
        $LoginCaptcha->ImageWidth = 200;
        $LoginCaptcha->CodeLength = 4;
        $LoginCaptcha->CodeStyle = CodeStyle::Alpha;

        // only show the Captcha if it hasn't been already solved 
        // for the current message
        if(!$LoginCaptcha->IsSolved) { ?>
          <label for="CaptchaCode">Retype the characters from the picture:</label>
          <?php echo $LoginCaptcha->Html(); ?>
          <input type="text" name="CaptchaCode" id="CaptchaCode" 
            class="textbox" /><?php

          // CAPTCHA validation failed, show error message
          if ('Captcha' == $error) { ?>
            <span class="incorrect">Incorrect code</span><?php
          }
        }
      ?>
      </div>

      <input type="submit" name="SubmitButton" id="SubmitButton" value="Submit"  />

    </fieldset>
  </form>
</body>
</html>

The example login form is simple, containing textboxes for username and password input, and remembering the username even when form validation fails (the password input is not remembered for obvious reasons). We add Captcha protection to the form using the standard procedure.

Note that we use the IsSolved property of the Captcha object to only display Captcha protection if it hasn't already been solved.

process-login.php

<?php 
  session_start(); 
  require("botdetect.php"); 

  $form_page = "index.php";
  
  // directly accessing this script is an error
  if (!$_SERVER['REQUEST_METHOD'] == "POST") {
    header("Location: ${form_page}");
    exit;
  }

  // sumbitted login data
  $username = $_REQUEST['Username'];
  $password = $_REQUEST['Password'];
  
  
  // CAPTCHA user input validation 
  $LoginCaptcha = new Captcha('LoginCaptcha');
  $LoginCaptcha->UserInputID = 'CaptchaCode';
  if (!$LoginCaptcha->IsSolved) {
    $isHuman = $LoginCaptcha->Validate();
    if (!$isHuman) {
      // CAPTCHA validation failed, show error message
      $form_page = $form_page . '?Username=' . urlencode($username) .
        "&error=Captcha";
      header("Location: ${form_page}");
      exit;
    }
  }
  
  // CAPTCHA validation passed, only now do we perform the protected action 
  // (try to authenticate the user)

  // check login format
  $isValidLogin = ValidateLogin($username, $password);
  if (!$isValidLogin) {
    // invalid login format, show error message
    $form_page = $form_page . '?Username=' . urlencode($username) . 
      "&error=Format";
    header("Location: ${form_page}");
    exit;
  }
  
  // authenticate the user
  $isAuthenticated = Authenticate($username, $password);
  if (!$isAuthenticated) {
    // failing authentication 3 times shows the Captcha again
    $count = 0;
    if (isset($_SESSION['FailedAuthCount'])) {
      $count = (int) $_SESSION['FailedAuthCount'];
    }
    $count++;
    if ($count > 2) {
      $LoginCaptcha->Reset();
      $count = 0;
    }
    $_SESSION['FailedAuthCount'] = $count;
  
    // authentication attempt failed, show error message
    $form_page = $form_page . '?Username=' . urlencode($username) . 
      "&error=Auth";
    header("Location: ${form_page}");
    exit;
  }

  
  function ValidateLogin($p_Username, $p_Password) {
    $result = false;
    // we check both username and password are specified and alphanumeric
    if (strlen($p_Username) > 0 && strlen($p_Password) > 0) {
      $pattern = '/^[a-zA-Z0-9_]+$/'; // alphanumeric chars and underscores only
      $result = (1 == preg_match($pattern, $p_Username));
      $result &= (1 == preg_match($pattern, $p_Password));
    }
    return $result;
  }
  
  function Authenticate($p_Username, $p_Password) {
    $result = false;
    // Since this is a simple example project, we consider all authentication
    // attempts with usernames and passwords longer than 5 characters valid 
    // instead of looking up the info in a database etc.
    if (strlen($p_Username) > 4 && strlen($p_Password) > 4) {
      $result = true;
    }
    return $result;
  }
  
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
  <title>BotDetect PHP CAPTCHA Validation:
    PHP Login Form CAPTCHA Code Example</title>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <link type="text/css" rel="Stylesheet" href="stylesheet.css" />
</head>
<body>
  <div class="column">
    <h1>BotDetect PHP CAPTCHA Validation: <br /> 
      PHP Login Form CAPTCHA Code Example</h1>
    
    <h2>Protected Page</h2>

    <fieldset id="Properties">
      <legend>Validation passed!</legend>
      
      <div class="input">
        <label for="Username">Username:</label>
        <input name="Username" id="Username" type="text" class="textbox" 
          readonly="readonly" value="<?php echo urlencode($username); ?>" />
      </div>
      
      <div class="input">
        <label for="Password">Password:</label>
        <input name="Password" id="Password" type="text" class="textbox" 
          readonly="readonly" value="<?php echo urlencode($password); ?>" />
      </div>
      
      <p class="navigation">
        <?php // Example only, we want to show the Captcha again when returning 
        // to the form
          $LoginCaptcha->Reset() ?>
        <a href="index.php">Back to login page</a>
      </p>
    </fieldset>
  </div>
</body>
</html>

The login attempt processing code can only be accessed by posting the login form, and redirects back to in when accessed directly - or when from validation fails.

We need to include the BotDetect Captcha library, and create a Captcha object with the same CaptchaId ('LoginCaptcha') and UserInputID ('CaptchaCode') values as were used to show the Captcha on the login form.

Since we want to reset the Captcha status after 3 failed authentications, we keep the related counter in the FailedAuthCount Session value.

This allows us to implement the proper Login form workflow:

  • The user must first solve the Captcha to prove they are human. This keeps the bots away from the authentication code, both conserving its resources and improving its security (since usernames and passwords will not be forwarded to the underlying data store if the Captcha is not solved first).
  • When the user has proven they are human, they get 3 authentication attempts without new Captcha tests, which allows them to remember the right combination in most cases.
  • If the user fails three authentication requests, they are shown a new Captcha which they must solve before continuing. This throttles authentication access, ensuring username + password combinations cannot be brute-forced, while real human users get theoretically unlimited authentication attempts (as long as they don't mind solving further Captchas).

As previously mentioned, the actual authentication code is not implemented, but the example code should demonstrate the underlying principles adequately.

After successful authentication, the example just displays the user input (while a real login form would set an auth cookie and redirect the user to actual functionality).