BotDetect CAPTCHA Validation ASP Code Sample

The CAPTCHA validation sample contains the basic code required to show a CAPTCHA on a ASP page and validate the user input, allowing users access to a protected page only if they successfully solve the CAPTCHA.

It can be used as a starting point when you are first learning how to use BotDetect, and is equivalent to the result you will get if you are following the How To add BotDetect CAPTCHA protection to ASP forms instructions.

Sample Project Location

By default, this sample project is installed at
c:\Program Files\Lanapsoft\BotDetect\ASP\v2.0\Samples\CaptchaValidation\.

You can also run it from the Start Menu:
Programs > Lanapsoft > BotDetect > ASP > v2.0 > Samples > CAPTCHA Validation Sample.

BotDetectFormDemo.asp

Full Source Code Listing

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
  <head>
    <title>BotDetect CAPTCHA ASP Form Demo - Input Page</title>
    <link type='text/css' rel='Stylesheet' href='FormStyle.css' />
    <script type="text/javascript" src="BotDetectScript.js"></script>
  </head>
  <body>
    <form name="SampleForm" id="SampleForm" method="post" 
      action="ProcessForm.asp">
    <fieldset id="SampleFields">
      <legend>Sample input form</legend>
      <div class="input">
        <label for="FirstName">First Name:</label>
        <input name="FirstName" id="FirstName" type="text" 
          class="textbox" value="<%=Request("FirstName") %>" />
      </div>
      <div class="input">
        <label for="LastName">Last Name:</label>
        <input name="LastName" type="text"  id="LastName" 
          class="textbox" value="<%= Request("LastName") %>" />
      </div>
    </fieldset>
    <fieldset id="CaptchaValidation">
      <legend>CAPTCHA Validation</legend>
      <div id="PromptDiv">Retype the code from the picture</div>
      <div id="CaptchaDiv">
        <div id="CaptchaImage">
          <img id="SampleForm_CaptchaImage" src="
            LanapBotDetectHandler.asp?Command=CreateImage&
            TextStyle=4&ImageWidth=238&imageHeight=50&
            CodeLength=5&CodeType=0" alt='CAPTCHA Code Image' />
        </div>
        <div id="CaptchaIcons">
          <a href='LanapBotDetectHandler.asp?Command=CreateSound' 
            onclick='LBD_LoadSound("SampleForm_SoundPlaceholder", 
            "LanapBotDetectHandler.asp?Command=CreateSound");
            return false;' title="Speak the code"><img src="
            speaker.gif" alt="Speak the code" /></a>
          <a href='#' onclick='LBD_ReloadImage(
            "SampleForm_CaptchaImage"); return false;' title="
            Change the code"><img src="reload.gif" alt="Change the 
            code" /></a>
          <div id='SampleForm_SoundPlaceholder' 
            class="placeholder">&nbsp;</div>
        </div>
      </div>
      <div class="input">
        <label for="CaptchaCode">Code:</label>
        <input name="CaptchaCode" id="CaptchaCode" type="text" 
          class="textbox" onkeyup="this.value = 
          this.value.toLowerCase();" />
      </div>
      <%
      If (Request("WrongCode")<>"") Then
        Response.Write("<div><span id='CodeIncorrectLabel'>
          Incorrect code</span></div>")
      End If
      %>
      </fieldset>
      <div id="ActionDiv">
        <input type="submit" name="ProcessForm" value="Process Form" 
          id="ProcessForm" />
      </div>
      <div id="Note">
        <span>NOTE: the Trial version will use "LANAP" instead of a 
          random code in 50% of CAPTCHA images.</span>
      </div>
    </form>
  </body>
</html>

Explanation

Several Html elements are involved in adding BotDetect CAPTCHA protection to the ASP form:

  • In the <head> section of the document, we include a stylesheet which, among other things, defines the CAPTCHA elements layout (FormStyle.css), and the JavaScript file containing BotDetect helper functions for playing the audio CAPTCHA and reloading the CAPTCHA image (BotDetectScript.js).
  • We include the CAPTCHA image in the <body> of the page where we want it to be displayed, simply specifying LanapBotDetectHandler.asp?Command=CreateImage as the image source, and passing any optional image settings in the querystring.
  • Next to the CAPTCHA image we show a couple of icons allowing the users to use the sound CAPTCHA if they perfer it to the image one, or change the CAPTCHA image in case they find it hard to read. These icons link to JavaScript calls of functions from the included client-script library. The JavaScript sound playback also requires and empty placeholder <div>, where the sound <object>/<embed> element will be created on the speaker icon click.
  • Also, note that the sound CAPTCHA link executes the JavaScript call and aborts following the link (with return false;) – if users have JavaScript disabled or use a browser which doesn't support it, the speaker icon will behave like a normal link and fall back to sending the audio file with the spoken CAPTCHA code to the user, for download or playback in the default application associated with .wav files on their system.
  • There is also a small JavaScript fragment associated with the onkeyup event of the CAPTCHA code textbox, which automatically lowercases the user input as they type. This isn't strictly neccesary for the CAPTCHA to function, but it is useful to communicate that the CAPTCHA code isn't case-sensitive to the users. Since solving a CAPTCHA is always a disruption in the user experience (but in principle a smaller one than wading through heaps of bot-submitted spam), it's worth finding as many ways as possible to improve the CAPTCHA usability and minimize the impact it has on the user experience of your form.
  • The form also contains a couple of example input fields beside the CAPTCHA, simply to demonstrate how to process user input only if the CAPTCHA is solved correctly, and how to persist the user-submitted values in case they fill out the form but enter an incorrect CAPTCHA code.

ProcessForm.asp

Full Source Code Listing

<%
  'Captcha validation
  Dim result, codeKey, inputCode
  result = False
  codeKey = "LanapBotDetectCode"
  inputCode = Request("CaptchaCode")

  If (Session(codeKey)<>"") Then
    code = Session(codeKey)
    result = (0 = StrComp(inputCode, code, 1))
    'each Captcha code can only be validated once
    Session(codeKey) = ""
  End If

  If result = False Then
    first_name = Request("FirstName")
    last_name = Request("LastName")
    redirect_url = "BotDetectFormDemo.asp?FirstName=" + _
      first_name + "&LastName=" + last_name + "&WrongCode=WrongCode"
    Response.Redirect redirect_url
  End If
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
  <title>BotDetect CAPTCHA ASP Form Demo - Protected Page</title>
  <link type='text/css' rel='Stylesheet' href='FormStyle.css' />
</head>
<body>
  <form name="form1" method="post" id="form1" 
    action="ProcessForm.asp">
    <fieldset id="Properties">
      <legend>BotDetect CAPTCHA validation passed!</legend>
      <div class="input">
        <label for="FirstName">First Name:</label>
        <input name="FirstName" id="FirstName" type="text" 
          class="textbox" readonly="readonly" 
          value="<% =Request("FirstName") %>" />
      </div>
      <div class="input">
        <label for="LastName">Last Name:</label>
        <input name="LastName" id="LastName" type="text" 
          class="textbox" readonly="readonly" 
          value="<% =Request("LastName") %>" />
      </div>
    </fieldset>
    <div id="ActionDiv">
      <a href="BotDetectFormDemo.asp">Back to the sample form</a>
    </div>
  </form>
</body>
</html>

Explanation

When users fill out the sample form and submit it, this script is used for submitted data processing. At the very beginning of the file, we validate the CAPTCHA, comparing the user-entered CAPTCHA code to the value stored in Session state. If the CAPTCHA code is not correct, we immediately redirect the user back to the input form. Other field values beside the CAPTCHA are persisted in the querystring for simplicity. There are several things to note about this CAPTCHA validation code:

  • Always clear the CAPTCHA code saved in Session state after validation, regardless whether it was successful or not. CAPTCHA images are not meant to be cached or reused, and every form load should use a different CAPTCHA code. If the users solved the CAPTCHA correctly but you need to return them to the input form because one of the other input fields was not entered correctly, remember that the CAPTCHA validation was passed and don't show it on the form anymore. This can be achieved by setting a simple flag (Session("IsHumanUser") = True) and checking it before displaying the CAPTCHA.
  • Since the correct CAPTCHA code is stored in ASP Session state, you must ensure that ASP Session state is enabled and working properly for your ASP application. ASP Session state is always persisted in the IIS process memory (which means it can not be shared between processes or servers, i.e. in a web garden or a web farm), and cookie-based (users are returned to their Session data based exclusively on the ASP Session Cookie value).
  • In this example, there is only one page protected with the CAPTCHA, which can not be accessed without supplying the correct CAPTCHA code. If you have a series of pages following the input form, and you would like them all to be protected by the same CAPTCHA, you should set a flag after a successful CAPTCHA validation (Session("IsHumanUser") = True) and check it on every subsequent page (redirecting back to the input form if it's not set). Otherwise, malicious users could access these other pages by requesting them directly by Url.
  • Users with disabled cookies or without cookie support will always have an empty Session state container, which means they will never be able to solve the CAPTCHA correctly, even if they enter the exact code as shown in the CAPTCHA image. If a significant portion of your users visit your web application with disabled cookies, you might want to investigate some cookieless ASP Session state alternatives. While this will stop bots trying to post spam, it will also stop some potentially useful bots (e.g. Googlebot). Keep in mind that any pages behind the CAPTCHA protection (not the pages where the CAPTCHA is shown, but those accessible only after successfull CAPTCHA validation) will never be crawled, if the CAPTCHA is implemented properly.
  • This default CAPTCHA validation code assumes you have the CAPTCHA installed on only one form within the ASP application, since it uses a single Session state key to save the CAPTCHA codes (Session("LanapBotDetectCode")). If you have the CAPTCHA installed on multiple forms within the same application, you should give each of them a unique CAPTCHA identifier (e.g. "Registration" and "Comment"), and append that identifier to the Session state key in the validation code, as well as the querystring of all LanapBotDetectHandler.asp requests (e.g. LanapBotDetectHandler.asp?Command=CreateImage&CaptchaId=Registration). This is necessary to prevent different CAPTCHAs from overwriting each other's codes when different pages are opened at the same time (in multiple tabs of the same browser).

LanapBotDetectHandler.asp

Full Source Code Listing

<%
Dim code, codeKey, codeHash, codeHashKey, captchaId, comCaptcha

'the Captcha code is kept in Session state with this key
codeKey = "LanapBotDetectCode"
codeHashKey = "LanapBotDetectCodeHash"

'if there are multiple Captchas on tn the site, a Captcha id 
'is required to distinguish between them; otherwise, it can 
'be ignored
captchaId = Request("CaptchaId")
If(captchaId<>"") Then
  codeKey = codeKey & "_" & captchaId
End If

If (Request("Command")="CreateImage") Then
'Captcha image generation

  'create the Captcha component instance
  Set comCaptcha = CreateObject("Lanap.BotDetect")

  'process Captcha properties
  If (Request("TextStyle")<>"") Then 'set Captcha algorithm
    On Error Resume Next
    comCaptcha.TextStyle = CLng(Request("TextStyle"))
    Err.Clear
  End If
  If (Request("ImageWidth")<>"") Then  'set Captcha image width
    On Error Resume Next
    comCaptcha.ImageWidth = CLng(Request("ImageWidth"))
    Err.Clear
  End If
  If (Request("ImageHeight")<>"") Then  'set Captcha image height
    On Error Resume Next
    comCaptcha.ImageHeight = CLng(Request("ImageHeight"))
    Err.Clear
  End If
  If (Request("CodeLength")<>"") Then  'set Captcha code length
    On Error Resume Next
    comCaptcha.CodeLength = CLng(Request("CodeLength"))
    Err.Clear
  End If
  If (Request("CodeType")<>"") Then  'set Captcha code type
    On Error Resume Next
    comCaptcha.CodeType = CLng(Request("CodeType"))
    Err.Clear
  End If
  If (Request("Format")<>"") Then  'set Captcha image format
    On Error Resume Next
    comCaptcha.Format = Request("Format")
    Err.Clear
  End If

  'set Captcha image Http response headers
  Response.Buffer = True
  Response.CacheControl = "no-cache, no-store, must-revalidate"
  Response.AddHeader "Pragma", "no-cache"
  Response.Expires = -1
  If (comCaptcha.Format="JPEG") Then
    Response.ContentType = "image/jpeg"
  ElseIf (comCaptcha.Format="PNG") Then
    Response.ContentType = "image/png"
  ElseIf (comCaptcha.Format="GIF") Then
    Response.ContentType = "image/gif"
  ElseIf (comCaptcha.Format="BMP") Then
    Response.ContentType = "image/bmp"
  End If

  'generate the Captcha image binary data
  Dim varPicture
  varPicture = comCaptcha.CreateImage

  'save the Captcha code for sound generation and validation
  code = comCaptcha.GetValue
  Session(codeKey) = code
  'save the code hash for backward compatibility with older 
  'validation code
  codeHash = comCaptcha.GetHashValue
  Session(codeHashKey) = codeHash

  'send Captcha image binary data to the client
  Response.BinaryWrite varPicture
  Set comCaptcha = Nothing  'dispose of the Captcha component instance
  Response.End
'end Captcha image generation

ElseIf (Request("Command")="CreateSound") Then
'audio Captcha generation

  'create the Captcha component instance
  Set comCaptcha = CreateObject("Lanap.BotDetect")

  'set Http response headers
  If (Request.ServerVariables("HTTPS")="off") Then
    Response.CacheControl = "no-cache"
    Response.AddHeader "Pragma", "no-cache"
    Response.Expires = -1
  End If
  Response.Buffer = True
  Response.ContentType = "audio/x-wav"
  Response.AddHeader "content-disposition", _
    "attachment; filename=captcha.wav"
  Response.AddHeader "Content-Transfer-Encoding", "binary"
  Response.AddHeader "Connection", "Close"

  'generate the audio Captcha binary data from the saved code
  code = Session(codeKey)
  varSound = comCaptcha.CreateSoundFromCode(code)

  'send audio Captcha binary data to the client
  Response.BinaryWrite varSound
  Set comCaptcha = Nothing 'dispose of the Captcha component instance
  Response.End
'end audio Captcha generation

ElseIf (Request("Command")="Validate") Then
'Ajax Captcha validation

  Dim result
  result = False

  If (Session(codeKey)<>"") Then
    Dim inputCode
    inputCode = Request("Code")
    code = Session(codeKey)
    result = (0 = StrComp(inputCode, code, 1))
    'Ajax validation shouldn't remove the code if successful, so both 
    'client- and server-side validation can be performed and pass
    If (Not result) Then
      Session(codeKey) = ""
    End If
  End If

  'Http response headers
  Response.Buffer = True
  Response.ContentType = "text/javascript"
  Response.CacheControl = "no-cache, no-store, must-revalidate"
  Response.AddHeader "Pragma", "no-cache"
  Response.Expires = -1
  Response.AddHeader "Connection", "Close"

  'send the JSON validation result to the client
  Response.Write "{ 'result': " & LCase(CStr(result)) & " }"
  Response.End

'end Ajax Captcha validation
End If

'If neither of the above conditions was met
Response.Status = "400 Bad Request"
Response.End
%>

Explanation

This file is the central ASP module used to access CAPTCHA funtionality from your forms. It provides a simple Http interface to the Lanap.BotDetect COM component which is responsible for all CAPTCHA image and sound generation. All image settings are passed to the handler as querystring parameters. The LanapBotDetectHandler.asp module also exposes a validation Url returning the CAPTCHA validation result in simple JSON format, providing an access point for Ajax CAPTCHA validation.

Due to different requirements for image and sound files sent to the client, different caching Http headers are used. In particular, the CAPTCHA sound Http response detects SSL access to work around a bug in Internet Explorer (where the no-cache headers could prevent the browser form playing the sound file at all).

Note that the CAPTCHA code is generated and saved in Session state during the CAPTCHA image generation, which means the CAPTCHA code will not exist before the CAPTCHA image request is processed. So for example, it is not possible to access the sound CAPTCHA before the image CAPTCHA.

Sicne every ASP application (every IIS virtual directory configured as an application, with it's own Global.asa file, user supplied or default) has it's own Session state, and multiple ASP applications can not share their Session state contents, you can not reuse the LanapBotDetectHandler.asp file between multiple ASP applications – every application must have it's own copy of this file. It is possible to reuse this file between different IIS virtual directories, as long as they are all configured to run within the same ASP application.

BotDetectScript.js

Full Source Code Listing

function LBD_LoadSound(soundPlaceholderId, soundLink) {
  if(document.getElementById) {
    var i = soundLink.indexOf('&d=');
    if (-1 != i) {
      soundLink = soundLink.substring(0, i);
    }
    soundLink = soundLink + '&d=' + LBD_GetTimestamp();

    var placeholder = document.getElementById(soundPlaceholderId);
    var objectSrc = "<object id='captchaSound' 
      classid='clsid:22D6F312-B0F6-11D0-94AB-0080C74C7E95' 
      height='0' width='0' style='width:0; height:0;'><param 
      name='AutoStart' value='1' /><param name='Volume' value='0' 
      /><param name='PlayCount' value='1' /><param name='FileName' 
      value='" + soundLink + "' /><embed id='captchaSoundEmbed' 
      src='"+ soundLink + "' autoplay='true' hidden='true' 
      volume='100' type='"+ LBD_GetMimeType() +"' 
      style='display:inline;' /></object>";

    placeholder.innerHTML = "";
    placeholder.innerHTML = objectSrc;
  }
}

function LBD_GetTimestamp() {
  var d = new Date();
  var t = d.getTime() + (d.getTimezoneOffset() * 60000);
  return t;
}

function LBD_GetMimeType() {
  var mimeType = "audio/x-wav";
  return mimeType;
}

var LBD_ImgId = null;
var LBD_Img = null;
var LBD_NewImg = null;
var LBD_Parent = null;
var LBD_ImagePrompt = null;

function LBD_ReloadImage(imgId) {
  if(imgId) {
    LBD_ImgId = imgId;
    LBD_Img = document.getElementById(LBD_ImgId);
    var src = LBD_Img.src;

    var i = src.indexOf('&d=');
    if (-1 != i) {
      src = src.substring(0, i);
    }
    var newSrc = src + '&d=' + LBD_GetTimestamp();

    LBD_NewImg = document.createElement('img');
    LBD_NewImg.onload = LBD_ShowImage;
    LBD_NewImg.id = LBD_Img.id;
    LBD_NewImg.alt = LBD_Img.alt;
    LBD_NewImg.src = newSrc;

    LBD_ImagePrompt = document.createElement('span');
    LBD_ImagePrompt.appendChild(document.createTextNode('loading...'));

    LBD_Parent = LBD_Img.parentNode;
    LBD_Parent.removeChild(LBD_Img);
    LBD_Parent.appendChild(LBD_ImagePrompt);
  }
}

function LBD_ShowImage() {
  if(LBD_NewImg && LBD_Parent && LBD_ImagePrompt) {
    LBD_Parent.removeChild(LBD_ImagePrompt);
    LBD_Parent.appendChild(LBD_NewImg);
  }
}

Explanation

This JavaScript file includes functions for CAPTCHA sound playback and CAPTCHA image reloading. Neither of these is required, but using client-side code improves the usability of both.

The CAPTCHA sound can also be played without JavaScript (by downloading the sound file and playing it in the system sound player), but the LBD_LoadSound function allows the sound file to be played directly in the browser and on the page where the CAPTCHA is displayed, using the sound player plugin configured for the browser. Of course, this asumes users have a sound player plugin installed and configured. The CAPTCHA image could also be changed by reloading the whole page, but using the LBD_ReloadImage function to change it asynchronously makes for a better user experience.

Both functions use simple DOM manipulation to add elements to the page dynamically. To play the CAPTCHA sound, we add an <object> element pointing to it and add it to the placeholder <div>. An <embed> element is nested inside it for cross-browser compatibility, even if it's not approved by the W3C standards. When reloading the CAPTCHA image, we replace the old image with a simple prompt while the new image is loading, and show the new CAPTCHA when it's fully loaded and ready.

To avoid any caching problems and ensure a new CAPTCHA sound / image is really loaded from the server when the user clicks the appropriate icon, we append the request timestamp (up to the milisecond) as an additional querystring parameter. A few additional lines are used to check for old timestamp values and replace them (for example, when user clicks the Reload icon multiple times).

FormStyle.css

Full Source Code Listing

body {
  background-color: #EEEEFF;
  font-family: Verdana, Arial;
  font-size: 0.9em;
}

fieldset {
  padding: 0 10px 10px 10px;
  margin: 11px;
  width: 300px;
  display: block;
}

div.input {
  margin: 7px 0;
}

legend {
  padding: 5px;
  color: #999999;
}

label {
  display: block;
  width: 85px;
  float: left;
  text-align: right;
  padding-right: 5px;
}

input.textbox {
  width: 170px;
}

input.textboxSmall {
  width: 40px;
}

#CodeIncorrectLabel {
  color: Red;
}

#CodeCorrectLabel {
  color: Green;
}

#Note {
  padding: 0;
  margin: 11px;
  margin-bottom: -7px;
  width: 320px;
  font-size: 0.8em;
  color: Red;
}

#PromptDiv {
  padding: 0;
  margin: 0;
  margin-bottom: 8px;
}

#ActionDiv {
  padding: 0 0 10px 10px;
  margin: 11px;
  margin-right: 0;
  width: 314px;
  text-align:right;
}

fieldset #ActionDiv{
  padding: 0;
  margin: 0;
  width: auto;
  text-align:right;
}

#CaptchaDiv {
  margin: 0;
  padding: 0;
  width:265px;
  height:50px;
  padding-bottom: 5px;
}

#CaptchaImage {
  float: left;
  margin: 0;
  padding: 0;
  width:240px;
  height:50px;
}

#CaptchaIcons {
  width: 22px;
  height: 50px;
  float: right;
  text-align: left;
  margin: 0;
  padding: 0;
}

#CaptchaIcons img {
  border: 0;
  margin: 0;
  padding: 0;
  padding-bottom: 3px;
}

*html #CaptchaIcons img {
  margin-bottom: -2px;
}

.placeholder {
  visibility: hidden;
  width:0 !important;
  height:0 !important;
}

*html .placeholder {
  display: none !important;
}

#CaptchaPreviewDiv {
  margin: 0;
  padding: 0;
  padding-bottom: 5px;
}

div.FeaturesInput {
  margin: 7px 0;
}

div.FeaturesInput label {
  width: 110px;
}

Explanation

This stylesheet defines the appearence of the whole sample form, and not just the CAPTCHA elements – you can copy only the bolded declarations to your stylesheet for that purpose.

We fix the layout of the CAPTCHA elements so the speaker and reload icons are shown vertically to the right of the CAPTCHA image. If you use smaller CAPTCHA image heights and want the icons to be displayed horizontally instead of vertically, just adjust the #CaptchaIcons <div> width. The CAPTCHA image and the parent <div> have their dimensions defined to avoid layout changes when using the Reload button (when the CAPTCHA image is temporarily replaced with a <span> of different dimensions).

The placeholder <div> used to contain the sound element during audio CAPTCHA playback is made invisible by the appropriate declarations, since we want the CAPTCHA sound to play without any visual changes in the page. Since IE 6.0 behaves significantly different from other browsers regarding sound element visibility, a special IE 6.0 -only declaration (starting with *html to filter out other browsers) had to be used.