Ioannis Panagopoulos blog

Tutorials on HTML5, Javascript, WinRT and .NET

CAPTCHA design and implementation

by Ioannis Panagopoulos

In my first blogging days, I received a comment for a post saying how wonderful and special the thing I did was. I was thrilled! Then I received 5 more comments with exactly the same sentence and then 5 more and I realized that this was my first encounter with comment spamming.

In order to avoid this I present here a way to add a custom made CAPTCHA (Completely Automated Public Turing test to tell Computers and Humans Apart) in Blogengine.Net (1.4.5.0). The code below is based on ideas from the implementation of Blogengine.net itself and from here.

Of course, the same principle may be applied to any other web page.

Before starting the implementation, we need to understand  a bit about what happens within the two events of the user opening a blog post and the user pressing the "Save Comment" button.

  • When the post is loaded and the comments are about to be displayed, the Page_Load method of the CommentView.ascx.cs (in the User Controls folder) file is called.
  • Then when the user presses the "Save Comment" button, the AddComment function of the blog.js file is called which validates the entered data and prepares an argument string to send back to the page. The argument string contains all the information needed for the server to try to save the comment.
  • Finaly the RaiseCallbackEvent(string eventArgument) method of the CommentView.ascx.cs (in the User Controls folder) file is called and the eventArgument parameter contains the data sent from AddComment (argument variable). In this method the desicion on wether to accept or not the comment may be made.

The main idea behind implementing a CAPTCHA for this process is as follows:

  • Create a hidden field in the CommentView.ascx control (not visible to the user).
  • When the post is loaded and comments are displayed generate a random number, dynamically create the bitmap with the number, mess it up a little and store in the hidden field the result of a simple mathematical expression with this value (eg value+2).
  • Display this bitmap in the CommentView control and also provide a sentence with the required mathematical expression to be applied (eg Add 2 to the number above).
  • When the user presses the Button, send the submitted user's value through AddComment back to the page within the argument variable.
  • In the RaiseCallbackEvent(string eventArgument) method check to see wether the submitted value equals to the hidden control's value and accept or reject the comment.

The aforementioned process in code is as follows (three files will be affected CommentView.ascx, CommentView.ascx.cs and blog.js).

In CommentView.ascx  locate the line that creates the TextBox for the user to submit his/her webpage and after this line add:

<!--=============================================-->
 <!-- Added code for CAPTCHA -->
 <!--=============================================-->
 Enter the word 
 <table style="display:inline">
 <tr>
   <td>
       <img src="../captcha.jpg" alt="CAPTCHA word" />
   </td>
 </tr>
 <tr>
   <td>
       Add 1 to the number above
   </td>
 </tr>
 </table>
 <asp:TextBox runat="Server" ID="txtCAPTCHA" Width="150" abIndex="3"
          ValidationGroup="AddComment" />
 <asp:RequiredFieldValidator runat="server"
       ControlToValidate="txtCAPTCHA"
       ErrorMessage="Please enter the correct word"
       Display="dynamic"
       ValidationGroup="AddComment" />
 <br /><br />
 <!--============================================-->

 As you can see we need to generate the captcha.jpg image and we are expecting the user's value in txtCAPTCHA. Moreover, we correct  value should be the number+1.

In the same file we add:

<asp:HiddenField runat="server" ID="myCaptcha" />

This will hold the correct value. Finally in the same file we add:

var postAuthor = "<%=Post.Author %>"; (Locate this line)
var txtCAPTCHA=$("<%=txtCAPTCHA.ClientID %>"); (Add this line)
var nameBox = $("<%=txtName.ClientID %>"); (Locate this line)

This will enable us to send the value through avascript back to the page.

In blog.js file we locate the AddComment functiona and in the assignment of the argument variable we add the submitted CAPTCHA value:

var argument = author + "-|-" + email + "-|-" + website + "-|-" + country + "-|-" +
     content + "-|-" + notify + "-|-" + isPreview + "-|-" +
     captcha + "-|-" + txtCAPTCHA.value;

Now in the CommentView.ascx.cs file we need to implement the following method (do not foget to include the System.Drawing namespace):

 protected void CreateBitmap()

 {
      Random RandomCaptchaNumberGenerator = new Random();
      int RandomNumber = RandomCaptchaNumberGenerator.Next(1000, 9999);
      myCaptcha.Value = (RandomNumber + 1).ToString();
 
      Bitmap bitmap = new Bitmap(85, 35);
      Graphics Gr = Graphics.FromImage(bitmap);
      Font font = new Font("Arial", 16, FontStyle.Regular, GraphicsUnit.Pixel);
      Rectangle Rect = new Rectangle(0, 0, 100, 50);
      Gr.FillRectangle(Brushes.Red, Rect);
      Gr.DrawRectangle(Pens.Red, Rect);
      Gr.DrawString(RandomNumber.ToString(), font, Brushes.White, 20, 10);
      Random r = new Random();
      for (int i = 0; i < 10; i++)
      {
          Gr.DrawLine(Pens.White, (float)r.Next(1, 99), (float)r.Next(1, 49), 
                                                  (float)r.Next(1, 99), (float)r.Next(1, 49));
      }
 
 
      bitmap.Save(Server.MapPath("~/CAPTCHA.jpg"), System.Drawing.Imaging.ImageFormat.Jpeg);
      bitmap.Dispose();
      Gr.Dispose();
 }

The method generates a random number, stores the number in the hidden field and creates the bitmap.

This method will be called within the Page_Load method (when the control is loaded and is not postback):

if (!Page.IsPostBack && !Page.IsCallback)
    {
            CreateBitmap(); (Add this line) (...)
Finally in the RaiseCallbackEvent method we validate as follows:
if (!isPreview && myCaptcha.Value!= args[8])
{
     _Callback = "Please provide the correct number";
     return;
}

 A minor change is to improve a little the message displayed when the user provides a wrong captcha. In blog.js file, locate the function AppendComment. The parameter args contains the value of the _Callback property, Add the following the beginning:

if (args == "Please provide the correct number") {
        $("status").innerHTML = args;
        $("btnSaveAjax").disabled = false;
        return ;
    }

And that will do the trick. As always, any comments for improvement are highly appreciated.

 

kick it on DotNetKicks.com

Shout it

blog comments powered by Disqus
hire me