Build a Captcha using jQuery and Web API (Part 1)

If you search the internet for implementing a captcha using jquery, you will find many options (here) but none (that I can find) that use web.api and without cookies. Furthermore; allowing images to be refreshed without a full post back and all of this using encryption to prevent hacking the code. That is why I wrote this article. I love trying new things and having lots of options. I am sure there are other ways of doing this but this is from the ground up. I do need to give credit for the image creating from here. Thanks bpell where ever you are. It is important to realize that you could add any image creator at this point. The important part of this is that it returns an image in the form of a memory stream. I found his to work just fine for now. With the credits already given let’s get started.

Take a look at the flow of code.

imagecaptcha1

Step 1 : User calls up page.
Step 2 : Ajax call to a web.api
Step 3 : Web.api calls image drawing
Step 4 : Image drawing sends value and image back to web.api
Step 5 : Wep.api sends the image back as an image encoded as well as the answer in an encrypted string
Step 6 : Browser displays image in data:image/jpeg;base64 and saves encrypted key into a hidden field on the form.

From here the user enters the value and when they send the form again then the encrypted key along with typed text gets sent to the server for compare. The server decrypts the key and compares the two values. If they match then processing the request continues and if they do not match a message is sent back to the user that it was incorrect. Now that the idea is explained let’s get started in looking at the details.

First create a blank project. (I am using Visual Studio Professional 2012)

Then create 4 files as follows;

  1. Create an html web page and call it index.html
  2. Create a CaptchaImageCreator class
  3. Create a captchaCreate class
  4. Create an Encypting class

(In part 2 we will expand these to handle the decrypting response.)

The index.html should look like this

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <script>
        $(function() {
            $.get("/api/CaptchaImage/GetCaptcha", {})
                .done(function (data) {
                    $("#captcha").attr("src", "data:image/jpeg;base64," + data[0].CaptchaImage)
                    $("#hdnCaptcha").val(data[0].CaptchaText)
                })
                .fail(function () {
                         alert("error");
                     })
                .always(function () {
              });

            $("#refresh-captcha").on("click",function(){
                $.get("/api/CaptchaImage/GetCaptcha", {})
                    .done(function (data) {
                        $("#captcha").attr("src", "data:image/jpeg;base64," + data[0].CaptchaImage)
                        $("#hdnCaptcha").val(data[0].CaptchaText)
                    })
                    .fail(function () {
                        alert("error");
                    })
                    .always(function () {
                    });
            })

        })
        </script>

Some points to make about this is that…..
1. for purpose of simplicity the refresh and the initial creation of the image are the same code so you could combine them into one jquery function. Heck, you could get really bold and make it a plugin.

2. I am using the data:image/jpeg;base64 for a couple of reasons. One is that this images are small enough that memory should not be a problem. Secondly, it makes using web.api a little easier and finally, because I thought it was a cool idea. Seriously, keep in mind that this way of using images does not work in all browsers (see here) so keep this in mind.

3. I am using an html element with an id of hdnCaptcha to store the encrypted key. You could use a cookie but if you don’t have to then why do it.

The CaptchaImageController (web api) class looks like this

mports System.Net
Imports System.Web.Http
Imports System.Net.Http
Imports System.Net.Http.Headers

Public Class CaptchaImageController
    Inherits ApiController

    ' GET api/<controller>/5
    Public Function GetCaptcha() As IEnumerable(Of Captcha)

        Dim EncryptKey As String = "CaptchaTest"

        Dim captcha As New CaptchaCreate()
        Dim ms As New System.IO.MemoryStream
        captcha.CreateImage(ms)

        Dim Captchas As New System.Collections.ObjectModel.Collection(Of Captcha)

        Dim wrapper As New Encrypting(EncryptKey)
        Dim cipherText As String = wrapper.EncryptData(captcha.Text)

        Captchas.Add(New Captcha(cipherText, Convert.ToBase64String(ms.ToArray)))

        Return Captchas

    End Function
End Class
Public Class Captcha
    Public CaptchaText As String
    Public Property CaptchaImage As String
    Public Sub New(CaptchaText As String, CaptchaImage As String)
        Me.CaptchaText = CaptchaText
        Me.CaptchaImage = CaptchaImage

    End Sub
End Class

Some things about this file. I used the encryption key called “CaptchaTest”. Ideally you would store that key in the web.config or some other method. I am also returning the results in a json type response instead of an httpresponse.

The next file is called “CaptchaCreate”. This is the class that creates the image that will be sent back. (see above for author credits).


Imports System
Imports System.Text
Imports System.Drawing
Imports System.Drawing.Graphics
Imports System.Drawing.Imaging
Imports Microsoft.VisualBasic

' http://social.msdn.microsoft.com/Forums/vstudio/en-US/c6a3b8f5-045c-4828-9c2b-56eab5f8680a/inserting-captcha-image-to-web-form?forum=vbgeneral
' Class that creates a Captcha Image 

' Use it as follows ***
'     Dim captcha As New Captchas()
'Dim ms As New System.IO.MemoryStream
'captcha.CreateImage(ms)

''' <summary>
''' Class can be used to validate whether a user is a user or a bot.
''' </summary>
''' <remarks></remarks>
''' <dependencies>
''' System.Drawing
''' </dependencies>
Public Class CaptchaCreate
    '*********************************************************************************************************************
    '
    '             Class:  Captcha
    '      Initial Date:  04/12/2008
    '      Last Updated:  04/14/2009
    '     Programmer(s):  Blake Pell
    '
    '*********************************************************************************************************************

    ''' <summary>
    ''' Constructor
    ''' </summary>
    ''' <remarks></remarks>
    '''
    Public Sub New()

    End Sub

    ''' <summary>
    ''' Creates the skewed image with text and puts it into a memory stream.
    ''' </summary>
    ''' <param name="MemoryStream"></param>
    ''' <remarks></remarks>
    Public Sub CreateImage(ByRef memoryStream As System.IO.MemoryStream)
        _text = GetRandomText()
        Dim bitmap As New Bitmap(150, Width, System.Drawing.Imaging.PixelFormat.Format32bppArgb)
        Dim g As Graphics
        g = Graphics.FromImage(bitmap)
        Dim rect As New Rectangle(0, 0, 200, 70)
        Dim counter As Integer = 0
        g.FillRectangle(FillColor, rect)
        Dim i As Integer
        For i = 0 To _text.Length - 1
            g.DrawString(_text(i).ToString, _font, GetRandomBrush, New PointF(10 + counter, 10))
            counter += 20
        Next
        DrawRandomLines(g)
        bitmap.Save(memoryStream, ImageFormat.Jpeg)

        ' Cleanup
        g.Dispose()
        bitmap.Dispose()
    End Sub

    ''' <summary>
    ''' Draws random lines via a graphics object.
    ''' </summary>
    ''' <param name="g"></param>
    ''' <remarks></remarks>
    Private Sub DrawRandomLines(ByVal g As Graphics)
        Dim i As Integer
        For i = 0 To 10
            g.DrawLine(New Pen(Color.Gray, 1), GetRandomPoint(), GetRandomPoint2())
        Next
    End Sub

    ''' <summary>
    ''' Gets a random point within the top half of the image boundaries
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GetRandomPoint() As Point
        Return New Point(_random.Next(0, 100), _random.Next(1, 25))
    End Function

    ''' <summary>
    ''' Gets a random point within the bottom half of the image boundaries
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GetRandomPoint2() As Point
        Return New Point(_random.Next(101, 200), _random.Next(26, 50))
    End Function

    ''' <summary>
    ''' Gets a random brush color
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GetRandomBrush() As Brush
        Select Case _random.Next(1, 5)
            Case 1
                Return Brushes.Blue
            Case 2
                Return Brushes.Black
            Case 3
                Return Brushes.Red
            Case 4
                Return Brushes.Green
            Case 5
                Return Brushes.Maroon
            Case Else
                Return Brushes.White
        End Select
    End Function

    ''' <summary>
    ''' Gets random text.
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GetRandomText() As String
        Dim randomText As New StringBuilder
        Dim characterSet As String = "abcdefghijkmnopqrstuvwxyz23456789"    'Excluded l and 1 because they are similar
        For counter As Integer = 0 To Length
            randomText.Append(characterSet(_random.Next(characterSet.Length)))
        Next
        Return randomText.ToString
    End Function

    '**********************************************************************************************
    '  Properties and class variables
    '**********************************************************************************************

    ''' <summary>
    ''' Random number generator.
    ''' </summary>
    ''' <remarks></remarks>
    Private _random As New System.Random

    Private _text As String = ""
    Private _length As Integer = 2
    Private _width As Integer = 70
    Private _FillColor As Brush = Brushes.DarkKhaki

    ''' <summary>
    ''' Text to display on the image.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>Currently, this is set to the value of the GetRandomText function when the CreateImage procedure is run.</remarks>
    Public Property Text() As String
        Get
            Return _text
        End Get
        Set(ByVal value As String)
            _text = value
        End Set
    End Property

    Private _fontSize As Integer = 10
    ''' <summary>
    ''' The font size to use on the image.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>The default font size is 10</remarks>
    Public Property FontSize() As Integer
        Get
            Return _fontSize
        End Get
        Set(ByVal value As Integer)
            _fontSize = value
        End Set
    End Property

    Private _font As New Font("Courier New", _fontSize + _random.Next(14, 18), FontStyle.Bold)
    ''' <summary>
    ''' The font to use on the image.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>The default font is Courier New.</remarks>
    Public Property Font() As Font
        Get
            Return _font
        End Get
        Set(ByVal value As Font)
            _font = value
        End Set
    End Property
    ''' <summary>
    ''' The number of characters in Captcha
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>The default length is 2</remarks>
    Public Property Length As Integer
        Get
            Return _length
        End Get
        Set(value As Integer)
            _length = value
        End Set
    End Property
    ''' <summary>
    ''' Width of the Captcha image in px
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>The default width is 70</remarks>
    Public Property Width As Integer
        Get
            Return _width
        End Get
        Set(value As Integer)
            _width = value
        End Set
    End Property
    ''' <summary>
    ''' Color of the background of the Captcha
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>The default is Dark Kahki</remarks>
    Public Property FillColor As Brush
        Get
            Return _FillColor
        End Get
        Set(value As Brush)
            _FillColor = value
        End Set
    End Property

End Class

Some points to make with this. You can set different properties and create your own. For example I removed the 1 and the l from the list of random strings because they looked similar. I also added properties for width and height and changed the number of characters. Your options are limitless.

The last file is the encrypting file and is used to just encrypt the response string so that there is no spoofing the captcha. The code looks like this which comes from here (The decrypting will come in part 2)

Imports System.Security.Cryptography

Public Class Encrypting

    Private TripleDes As New TripleDESCryptoServiceProvider

    Sub New(ByVal key As String)
        ' Initialize the crypto provider.
        TripleDes.Key = TruncateHash(key, TripleDes.KeySize \ 8)
        TripleDes.IV = TruncateHash("", TripleDes.BlockSize \ 8)
    End Sub
    Private Function TruncateHash(
    ByVal key As String,
    ByVal length As Integer) As Byte()

        Dim sha1 As New SHA1CryptoServiceProvider

        ' Hash the key.
        Dim keyBytes() As Byte =
            System.Text.Encoding.Unicode.GetBytes(key)
        Dim hash() As Byte = sha1.ComputeHash(keyBytes)

        ' Truncate or pad the hash.
        ReDim Preserve hash(length - 1)
        Return hash
    End Function

    Public Function EncryptData(ByVal plaintext As String) As String

        ' Convert the plaintext string to a byte array.
        Dim plaintextBytes() As Byte =
            System.Text.Encoding.Unicode.GetBytes(plaintext)

        ' Create the stream.
        Dim ms As New System.IO.MemoryStream
        ' Create the encoder to write to the stream.
        Dim encStream As New CryptoStream(ms, TripleDes.CreateEncryptor(), System.Security.Cryptography.CryptoStreamMode.Write)

        ' Use the crypto stream to write the byte array to the stream.
        encStream.Write(plaintextBytes, 0, plaintextBytes.Length)
        encStream.FlushFinalBlock()

        ' Convert the encrypted stream to a printable string.
        Return Convert.ToBase64String(ms.ToArray)
    End Function

    Public Function DecryptData(ByVal encryptedtext As String) As String

        ' Convert the encrypted text string to a byte array.
        Dim encryptedBytes() As Byte = Convert.FromBase64String(encryptedtext)

        ' Create the stream.
        Dim ms As New System.IO.MemoryStream
        ' Create the decoder to write to the stream.
        Dim decStream As New CryptoStream(ms, TripleDes.CreateDecryptor(), System.Security.Cryptography.CryptoStreamMode.Write)

        ' Use the crypto stream to write the byte array to the stream.
        decStream.Write(encryptedBytes, 0, encryptedBytes.Length)
        decStream.FlushFinalBlock()

        ' Convert the plaintext stream to a string.
        Return System.Text.Encoding.Unicode.GetString(ms.ToArray)
    End Function
End Class

That should do it. Part 2 I will show you how to validate the response from the user. I have also attached the code here. (In this project there is also commented sections for implementing this with cookies.)

Code:captcha_Jquery

Looking for quality web hosting? Look no further than Arvixe Web Hosting!

Tags: , , , , , , , , , , | Posted under Programming/Coding | RSS 2.0

Author Spotlight

David Bauernschmidt

David Bauernschmidt

I live in the historical triangle of Virginia where I am married with two daughters. I have spent over 13 years working for a Fortune 500 company in the computer area. I started in VB 6.0 and by the time I ended my employment I was supervising a development team where we built many web applications. When my first daughter was born I wanted to spend more time with her so I left and became a programmer analyst for local government as well as launch my own company. Since then I have grown James River Webs into a profitable web design and application company helping small businesses create a big presence on the internet. As an employee I have created web application used by citizens and other companies. I enjoy fly fishing, and spending time with my family. I also enjoy learning new approaches and development tools when it comes to developing applications.

Leave a Reply

Your email address will not be published. Required fields are marked *


7 − = 2

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>