BotDetect CAPTCHA Ajax Validation ASP Code Sample
This sample demonstrates how to use Ajax asynchronous requests to improve the CAPTCHA validation user experience. It uses the jQuery library for making asynchonous requests and parsing the JSON result returned by the ASP validation code. When the user enters an incorrect CAPTCHA code, the error message is shown perciveably faster. For security reasons, passing the client-side CAPTCHA validation doesn't bypass the server-side validation.
- Sample Project Location
- BotDetectAjaxDemo.asp
- ProcessFormAjax.asp
- LanapBotDetectHandler.asp
- BotDetectScript.js
- BotDetectAjaxValidation.js
- FormStyle.css
Sample Project Location
By default, this sample project is installed at
c:\Program Files\Lanapsoft\BotDetect\ASP\v2.0\Samples\CaptchaAjaxValidation\.
You can also run it from the Start Menu:
Programs > Lanapsoft > BotDetect > ASP > v2.0 > Samples > CAPTCHA Ajax Validation Sample.
BotDetectAjaxDemo.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 Ajax Demo - Input Page</title>
<link type='text/css' rel='Stylesheet' href='FormStyle.css' />
<script type="text/javascript" src="BotDetectScript.js"></script>
<script type="text/javascript" src="BotDetectAjaxValidation.js">
</script>
<script type="text/javascript" src="jquery-1.2.3.pack.js">
</script>
</head>
<body>
<form name="SampleForm" id="SampleForm" method="post"
action="ProcessFormAjax.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">
</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>")
Else
Response.Write("<div><span id='CodeIncorrectLabel'
style='visibility:hidden;'>Incorrect code!</span></div>")
End if
%>
</fieldset>
<div id="ActionDiv">
<input type="submit" name="ProcessForm" value="Process Form"
id="ProcessForm" onclick="return LBD_Validate();" />
</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
The BotDetect Ajax CAPTCHA validation code is kept in the BotDetectAjaxValidation.js client-script file. Since we use jQuery for actual Ajax requests, we include both script files along with the standard one. Upgrading to newer versions of jQuery should be simple enough (just reference the newer version instead of the installed one). The CAPTCHA image and sound elements don't require any changes when modifying the validation code to use Ajax.
The actual CAPTCHA validation asynchronous call is executed from the button onclick handler in this sample. It could also be processed in the form onsubmit event handler, and if you perform client-side validation of other fields you should integrate this call with that code.
ProcessFormAjax.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 = "BotDetectAjaxDemo.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 Ajax 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="BotDetectAjaxDemo.asp">Back to the sample form</a>
</div>
</form>
</body>
</html>
Explanation
Server-side CAPTCHA validation code doesn't change when using Ajax client-side validation, so the same explanations and notes apply as in the regular CAPTCHA validation sample. Basically, since client-side validation is just an usability improvement and can not be used as the sole means of validation (since it's insecure by default and can easily be bypassed - as explained in this FAQ item), we always validate the CAPTCHA user input on the server. This is necessary to ensure proper CAPTCHA security.
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"
'DEBUG: used to test Ajax request timeout handling
'Dim objShell
'Set objShell = CreateObject("WScript.Shell")
'objShell.Popup "", 7, ""
'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
The Http interface used for Ajax CAPTCHA validation is included in the LanapBotDetectHandler.asp file by default. It is accessed by an Url like LanapBotDetectHandler.asp?Command=Validate&Code=ABCDE, and returns a simple JSON-formatted result ( { 'result': true } or { 'result': false } ).
The only major difference between CAPTCHA validation performed using Ajax is that when a user enters the correct code, we don't delete the correct code from the Session state. That way, the same code can be validated twice (first from client-side code using Ajax, and then again on the server-side when the whole form is submitted). Of course, the code gets deleted properly during server-side validation.
When the asynchronous CAPTCHA validation attempt fails, we still delete / change the CAPTCHA code. Otherwise bots could use this to attempt incorrect codes repeatedly for the same CAPTCHA image, allowing multiple validation attempts for the same code. This would be a major CAPTCHA security breach, so the code must be cleared upon failed Ajax validation.
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 is the standard BotDetect client-script used for sound playing and CAPTCHA image reloading, which is also used by the Ajax CAPTCHA validation code, without any changes from the non-Ajax use case.
BotDetectAjaxValidation.js
Full Source Code Listing
// element identifiers
var captchaImageId = 'SampleForm_CaptchaImage';
var codeTextboxId = 'CaptchaCode';
var errorLabelId = 'CodeIncorrectLabel';
var formId = 'SampleForm';
// other settings
var validationPrompt = 'validating...';
var ajaxTimeoutMiliseconds = 5000;
// shared variable declarations
var LBD_ValidationResult = false;
var LBD_ValidationRequest = null;
var LBD_CodeInput = null;
var LBD_ValidationPrompt = null;
var LBD_ErrorPrompt = null;
var LBD_PromptParent = null;
function LBD_Validate()
{
if(!LBD_ValidationResult)
{ // only validate the CAPTCHA if it isn't already passed
// init elements
LBD_CodeInput = document.getElementById(codeTextboxId);
LBD_ErrorPrompt = document.getElementById(errorLabelId);
LBD_PromptParent = LBD_ErrorPrompt.parentNode;
if(!LBD_CodeInput || !LBD_CodeInput.value ||
LBD_CodeInput.value.length < 0)
{ // validation fails if there is no user input
LBD_EndValidation(false);
LBD_CodeInput.focus();
}
else
{ // Ajax validation
LBD_StartValidation();
}
}
return LBD_ValidationResult;
}
function LBD_StartValidation()
{
// hide the error message
LBD_ErrorPrompt.style.visibility = 'hidden';
// show the validation status indicator
LBD_ValidationPrompt = document.createElement('span');
LBD_ValidationPrompt.id = 'LBD_ValidatingPrompt';
LBD_ValidationPrompt.appendChild(document.createTextNode(
validationPrompt));
LBD_PromptParent.appendChild(LBD_ValidationPrompt);
// send the user input to the server for validation
LBD_ValidateCode(LBD_CodeInput.value, LBD_EndValidation);
}
function LBD_ValidateCode(code, callback)
{
// set Ajax request timeout treshold
$.ajaxSetup( {
timeout : ajaxTimeoutMiliseconds
} );
// send CAPTCHA validation request
LBD_ValidationRequest = $.getJSON(
"LanapBotDetectHandler.asp", // server path
{ Command: "Validate", Code: code }, // querystring params
function(json) {
LBD_ProcessValidationResult(json.result, callback)
} // callback function
);
// if Ajax CAPTCHA validation timeouts, fall back to full
// form postback
$("#" + formId).ajaxError(function(LBD_ValidationRequest,
settings, err) {
LBD_PromptParent.removeChild(LBD_ValidationPrompt);
this.submit();
});
}
function LBD_ProcessValidationResult(result, callback)
{
// when the validation result arrives, remove validation status
// indicator
LBD_PromptParent.removeChild(LBD_ValidationPrompt);
if(!result)
{ // CAPTCHA validation failed, reset the CAPTCHA
LBD_ReloadImage(captchaImageId);
LBD_CodeInput.value = '';
}
if ("function" == typeof(callback))
{ // invoke the registered callback function
callback(result);
}
}
function LBD_EndValidation(result)
{
if(result)
{ // CAPTCHA validation passed, submit the form to the server
LBD_ErrorPrompt.style.visibility = 'hidden';
LBD_ValidationResult = true;
document.getElementById(formId).submit();
}
else
{ // CAPTCHA validation failed, show error message
LBD_ErrorPrompt.style.visibility = 'visible';
}
}
Explanation
The main purpose of Ajax CAPTCHA validation is to give users faster feedback when they enter an incorrect code, without affecting the rest of the page. So we use this script to process the user input, and if they passed the CAPTCHA validation submit the form normally (consenquentialy, we change the human user experience, without compromising the high security required when dealing with bots).
This improves the user experience when they enter an incorrect CAPTCHA code, at the price of validating it twice in case they enter it correctly. Since the benefits of this trade-off are quite percievable for the user, while the price affects only the code, this change is worth the effort. Also, handling of error cases (like entering an incorrect CAPTCHA code) has a greater impact on the overall usability of the form (since errors can easily frustrate or discourage users), so it's a good idea to "smooth-out" that part of the user experience.
Identifiers of all elements used for Ajax CAPTCHA validation are declared at the top of this script, so you should change them accordingly if your form uses different element names.
At the beginning of client-side validation we check does the CAPTCHA code input exist – if the user didn't enter any CAPTCHA code at all, we don't have to call back to the server to know the validation result. As defined in the LBD_EndValidation function, when the CAPTCHA validation fails we show the error message; if the validation is successful we hide the error message and submit the whole form to the server.
Since the CAPTCHA validation is performed asynchronously, there are separate functions to handle the start of the validation callback and the validation response from the server. Before sending the validation request we display a simple label ("validating..."), which is replaced by the validation result after the server response arrives. The jQuery call to execute the Ajax request is bolded. There is only elementary error handling used in this sample, falling back to full form postback in case the Ajax request timeouts or there is another error with it. Depending on your approach to error handling and general client-side validation, you should modify this script file to fit in with the rest of your application, this sample project is a very simple implementation for clarity's sake.
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;
}
td#CaptchaIcons {
padding-left: 3px;
}
#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
The style definitons used in this sample are the same as in the CAPTCHA validation sample.


