Click here to Skip to main content
Email Password   helpLost your password?

Introduction

AntiAuto is a set of controls that can be used on an ASP.NET Web Form to prevent automatic registrations from 'bots. It was something mentioned in the Lounge fairly recently and I thought it would be a good example for ASP.NET's superior application architecture.

Many sites require users to register before they're given full access. For example, Hotmail.com requires you to enter a few basic details to provide you with an email address. However, this type of service is open to abuse - the automatic generation of many email addresses to be used for sending spam by a piece of software that automatically completes the registration process generating random user names etc.

To prevent this type of abuse many sites require users to enter a code that is displayed in a picture -- thus forcing the person to enter the registration details.

The aim is to produce a set of ASP.NET control's that can be easily deployed, and will fit in around an existing registration procedure.

Solution Overview

To give an overview to the classes within the project, here's a quick UML class diagram.

The important classes being CodeImage and CodeImageValidator. Util is a supportive class, and provides the encryption and decryption functionality needed by various other parts.

CodeImage

The purpose of this class is to produce the HTML on the web form that will display the image. The image itself is generated by the DrawImage.aspx file, which in turn uses the PictureGenerator class.

CodeImageValidator

This is a BaseValidator derived class that verifies that the contents of the associated text box (ControlToValidate) matches what has been drawn within the CodeImage control (CodeImageControl).

How they work together

When a page is first loaded a random number is generated, this is then passed as part of a query string to the DrawImage.aspx page. This number is retained in ViewState throughout any postbacks that occur. During a postback the validation control (CodeImageValidator) is used to ensure that the form can be validated successfully.

Implementation Details

CodeImage

CodeImage is implemented in the CodeImage.cs file. The purpose of the control is to output the necessary HTML code to draw the image. For example, if the DrawImage.aspx page were to draw the picture the HTML output would look like:

<img src="DrawImage.aspx?code=12345">

The code for the entire class is as follows:

public class CodeImage : Control
{
    private string _key;
    private int _digits;
    public CodeImage()
    {
        _key = System.Configuration.ConfigurationSettings.AppSettings["EncryptionKey"];
        _digits = Int32.Parse(System.Configuration.ConfigurationSettings.AppSettings["Digits"]);
    }

    public string Code
    {
        get
        {
            return (string)ViewState["SecretCode"];
        }
        set
        {
            ViewState["SecretCode"] = value;
        }
    }

    protected override void Render(HtmlTextWriter output)
    {
        output.Write( String.Format("<img src=\"DrawImage.aspx?code={0}\">", 
                        HttpUtility.UrlEncode(Code)));
    }

    protected override void OnLoad(System.EventArgs e)
    {
        if (!this.Page.IsPostBack)
        {
            StringBuilder sbCode = new StringBuilder(_digits,_digits);

            Random R = new Random();
            int i;
            int MaxLimit = 9;
            for(i = 0; i < _digits; i++)
            {
                sbCode.Append(R.Next(MaxLimit));
            }
            Code = Util.EncryptString(sbCode.ToString(),_key);
        }
    }
}

It's extremely simple code, loading a couple of configuration settings in the Constructor. These are used across the various controls and pages, so using the web.config configuration file is the best way of doing it.

The Render method is overridden to write out the HTML code, the security code to be displayed in the picture comes from the Code property. Because the HTML code will be visible to users its necessary to encrypt the number before putting it into the QueryString, so instead of using code=12345 it would be code=jA89AlxmmA etc. This encryption is performed by the EncryptString method within the Util class. The code is then decrypted by the PictureGenerator class when its asked to render the image.

It's also important to note that the encrypted security code is UrlEncode'd first, ensuring that any invalid characters can be made safe before being placed into the QueryString. Again, these are then UrlDecode'd the other side (within DrawImage.aspx).

The OnLoad event is used to generate a random number if the page is being loaded for the first time (i.e. not within a Post Back). Once a security code has been generated it is stored in the Code property, which in turn stores it in the ViewState (thus allowing the contents to be persisted across postbacks).

The CodeImage control is to be put directly into an ASP.NET Web Form, and the code to do that is included later in the article.

CodeImageValidator

This class is implemented in the CodeImageValidator.cs file, and is used to check that the contents of the associated text box match the code generated by the CodeImage control.

Again, the code is relatively simple so here's the entire class implementation before the discussion:

public class CodeImageValidator : BaseValidator
{
    TextBox _codeTextBox;
    string _codeImageId;
    CodeImage _codeImageControl;
    string _key;


    public CodeImageValidator()
    {
        _key = System.Configuration.ConfigurationSettings.AppSettings["EncryptionKey"];
    }

    protected override bool ControlPropertiesValid()
    {
        // Should have a text box control to check

        Control ctrl = FindControl(ControlToValidate);
        Control imageControl = FindControl(_codeImageId);

        if ( (null != ctrl) && (null != imageControl) )
        {
            if ((ctrl is System.Web.UI.WebControls.TextBox) &&
                (imageControl is CodeImage))
            {
                _codeTextBox = (System.Web.UI.WebControls.TextBox) ctrl;
                _codeImageControl = (CodeImage) imageControl;

                return ( (null != _codeTextBox) && (null != _codeImageControl) );
            } 
            else
                return false;
        }
        else
            return false;
    }

    public string CodeImageControl
    {
        get
        {
            return _codeImageId;
        }
        set
        {
            _codeImageId = value;
        }
    }

    protected override bool EvaluateIsValid()
    {
        return (Util.DecryptString(_codeImageControl.Code,_key) == 
              _codeTextBox.Text);
    }
}

CodeImageValidator is derived from BaseValidator and so is expected to behave as any other Validation Control would. It needs to override the EvaluateIsValid method that will be called when the form is submitted.

The ControlPropertiesValid method is used to obtain references to the controls that are used during validation -- the TextBox and the CodeImage controls. These references are then maintained in the ctrl and imageControl fields. Two properties of the CodeImageValidator control are used to set these in the ASPX page, ControlToValidate and CodeImageControl.

The CodeImage Control has a property (Code) that contains the encrypted security code that is to be entered into the text box. This is then decrypted in the EvaluateIsValid method and compared to the text box's contents. If they match then the validation will have succeeded and the method can return true.

DrawImage.aspx

The DrawImage page is used to produce the JPEG stream to send to the browser and uses the PictureGenerator class. The code to the DrawImage page is as follows:

<%@ Page Language="C#" ContentType="image/jpeg" %>
<script language="C#" runat="server">
    void Page_Load (Object sender, EventArgs e)
    {
        // Draw the output

        Etier.AntiAuto.PictureGenerator.OutputPicture( this, 
                HttpUtility.UrlDecode(Request.QueryString["code"]) );
    }
</script>

First the ContentType is set to image/jpeg (ensuring that the browser renders the contents as an image, as opposed to an HTML page or any other type). The OutputPicture method is called, passing a reference to the current page and the encrypted security code to be rendered.

The code to the OutputPicture method is as follows:

public static void OutputPicture(System.Web.UI.Page page, string encryptedCode)
{
    page.Response.Clear();
            
    string key = System.Configuration.ConfigurationSettings.AppSettings["EncryptionKey"];
    int digits = Int32.Parse(System.Configuration.ConfigurationSettings.AppSettings["Digits"]);

    Bitmap codeBitmap = new Bitmap((digits*16)+20,26, PixelFormat.Format24bppRgb);
    Graphics g = Graphics.FromImage(codeBitmap);    

    g.SmoothingMode = SmoothingMode.AntiAlias;
    g.Clear(Color.Orange);

    g.DrawString( Util.DecryptString(encryptedCode,key), new Font("Arial", 16,
                  FontStyle.Bold ), SystemBrushes.WindowText, new Point(10,2));

    codeBitmap.Save( page.Response.OutputStream, ImageFormat.Jpeg);
            
    g.Dispose();
    codeBitmap.Dispose();
    page.Response.End();
}

The code is based on Nick Parker's "Web Graphics On The Fly in ASP.NET" article here at CodeProject. It loads the configuration settings and then renders the image, decrypting the security code first. The security code is taken from DrawImage.aspx's QueryString and was encrypted by the CodeImage control to prevent visitors determining the security code outside of the rendered picture.

How to use it

The demo application includes a ready-to-deploy sample, but there are a couple of things that would have to be done to an existing ASP.NET Web Form:

  1. Copy the AntiAuto assembly to the application's /bin directory
    Copy the web.config configuration file to the application's root
    Copy DrawImage.aspx to the same directory containing the Web Form
  2. Add an import command to the top of the web form:
    <%@ Register TagPrefix="etier" Namespace="Etier.AntiAuto" Assembly="AntiAuto" %>
    This maps a namespace to a tag prefix, such that you can then add controls in the form of <prefix:ControlClass.
  3. Add a CodeImage control to the web form:
    <etier:CodeImage
        ID="codeImageControl"
        RunAt="server"
    />
    At run time this produces the <img src="..."> HTML code to produce the image.
  4. Add a CodeImageValidator control to the web form:
    <etier:CodeImageValidator
        ID="codeImageValidator"
        ControlToValidate="codeTextBox"
        CodeImageControl="codeImageControl"
        ErrorMessage="Please enter the text in the image"
        Display="Static"
        RunAt="server"
    />
    It's important that the ControlToValidate and CodeImageControl are set to the Id's you gave to the TextBox and CodeImage Controls.

Conclusion

The controls are very simple and demonstrate how glorious ASP.NET's new architecture is. I hope people find it useful, and improve it. Apologies for not including many (if any) comments within the code, but since this article covers how it all works together it should be relatively to understand. As I revisit the solution I'll neaten things up a bit, one thing I would definitely like to do is produce a single control so that deployment is made even easier.

Please feel free to post a message below or email me if you have any questions or comments.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
Generalsome corrections
Tomas Kubes
0:21 16 Dec '08  
Nice article, but I have made some changes in my project on it:

Your code doesn't work in designer. It is better to inherit from CompareValidator like this:

public class CodeImageValidator : CompareValidator
{
protected override bool ControlPropertiesValid()
{
// Should have a text box control to check
TextBox codeTextBox = FindControl(ControlToValidate) as TextBox;
CodeImage imageControl = FindControl(ControlToCompare) as CodeImage;

return codeTextBox != null && imageControl != null;
}

protected override bool EvaluateIsValid()
{
TextBox codeTextBox = FindControl(ControlToValidate) as TextBox;
CodeImage imageControl = FindControl(ControlToCompare) as CodeImage;
return (Util.DecryptString(imageControl.Code, ConfigHelper.EncryptionKey) == codeTextBox.Text);
}
}

And picture is missing ContentType
Response.ContentType = "Image/jpeg";
GeneralThe image doesn't appear sometime
Steph2004
8:48 7 Jan '06  
Hi

This is a very great control, thanks a lot!

Any idea why the image doesn't appear sometime? If a refresh the browser, let's say every 5 seconds, 1 out of 5 times the image won't be visible, i.e it's a broken image.

Thanks,

Stephane


GeneralRe: The image doesn't appear sometime
Bollis
13:52 19 Nov '06  
I think the problem is because of saving the Code in the viewstate, if you change the logic for using Session instead, you won't have this behaviour.
GeneralRe: The image doesn't appear sometime
jacquesr
17:49 15 Apr '07  
The problem is that when the encoded base64 string has a '+' in it, it will be encoded fine, but then at the receiving end, UrlDecode will turn in back into a BLANK instead of a '+'. This will create a base64 string that will fail when decoded.
The solution is to use 'HtmlDecode' instead of 'UrlDecode' at the receiving end, in the aspx file that displays the captcha image.


jronaldi
AnswerRe: The image doesn't appear sometime
ktoc1
6:48 11 Jul '07  
Suspicious In the onload in the codeImage class I put a loop that keeps setting the code till the encrypted code contains no + symbol.


if (!this.Page.IsPostBack)
{
Code = "";
int t = Code.IndexOf("+");
do
{ StringBuilder sbCode = new StringBuilder(_digits,_digits);
Random R = new Random();
int i;
int MaxLimit = 9;
for(i = 0; i < _digits; i++)
{
sbCode.Append(R.Next(MaxLimit));
}

Code = Util.EncryptString(sbCode.ToString(),_key);
}
while(Code.IndexOf("+") > -1);

}

Cally
Questionerror creating control
kencodeproj
3:08 31 Dec '05  
I got an "error creating control" error on codeImageControl: Value cannot be null. Parameter name: String


Can you help me

Kenneth
AnswerRe: error creating control
xiezhenhua
0:00 17 Jun '06  
yes string no
AnswerRe: error creating control
xiezhenhua
0:01 17 Jun '06  
I got an "error creating control" error on codeImageControl: Value cannot be null. Parameter name: String


Can you help me

Kenneth
Roll eyes OMG Mad Unsure Hmmm Suspicious Suspicious Cool
GeneralBUG HttpUtility.UrlDecode(Request.QueryString["code"])
TaoZhao
11:24 14 Oct '05  
I believe this is a bug, UrlEncode convert '+' to %2d, however, UrlDecode will convert %2d to ' '

Etier.AntiAuto.PictureGenerator.OutputPicture( this, HttpUtility.UrlDecode(Request.QueryString["code"]) );

Here is my code.

CodeImage.cs
public string Code
{
get
{
//return (string)ViewState["SecretCode"];
return (string)this.Page.Session ["SecretCode"];
}
set
{
//ViewState["SecretCode"] = value;
this.Page.Session["SecretCode"] = value;
}
}



protected override void Render(HtmlTextWriter output)
{

//output.Write( String.Format("<img src=\"DrawImage.aspx?code={0}\">", HttpUtility.UrlEncode(Code) ));
output.Write(String.Format("<img src=\"DrawImage.aspx\">" ));
}

DrawImage.aspx.cs
protected void Page_Load(object sender, System.EventArgs e)
{
// Draw the output
//Etier.AntiAuto.PictureGenerator.OutputPicture( this, HttpUtility.UrlDecode(Request.QueryString["code"]) );
Etier.AntiAuto.PictureGenerator.OutputPicture( this, (string)Session["SecretCode"] );
}
AnswerRe: BUG HttpUtility.UrlDecode(Request.QueryString["code"])
Bryan Kadzban
6:58 5 May '06  
The problem is that Request.QueryString[...] calls UrlDecode for you. This turns the %2d back into a + character.

But then you call UrlDecode on that string again, which turns the + into a ' ' (a space). This is expected -- a space character UrlEncodes to a + character, so any + characters in the input to UrlDecode should turn back into spaces.

It's not exactly a bug in ASP.net, it's due to a lack of documentation (the docs for QueryString say nothing about whether UrlDecode is called) and an HttpUtility.UrlDecode call that shouldn't be there in the sample code for this article.
Generalexception while encoding text and random lines on picture
Cristian Romanescu
7:57 10 Jun '05  
1. I found that sometimes the code throws an exception during encoding to Base-64.
Screenshot here:
http://pg.photos.yahoo.com/ph/cristiroma/detail?.dir=59aa&.dnm=f0e2.jpg&.src=ph



Line 4: {
Line 5: // Draw the output
Line 6: Etier.AntiAuto.PictureGenerator.OutputPicture( this, HttpUtility.UrlDecode(Request.QueryString["code"]) );
Line 7: }
Line 8: </script>


[FormatException: Invalid length for a Base-64 char array.]
System.Convert.FromBase64String(String s) +0
Etier.AntiAuto.Util.DecryptString(String cipherText, String key)
Etier.AntiAuto.PictureGenerator.OutputPicture(Page page, String encryptedCode)
ASP.DrawImage_aspx.Page_Load(Object sender, EventArgs e) in D:\projects_websites\jobsmart\login\DrawImage.aspx:6 System.Web.UI.Control.OnLoad(EventArgs e) +67
System.Web.UI.Control.LoadRecursive() +35
System.Web.UI.Page.ProcessRequestMain() +750


2. Also added some code to draw random lines on the picture.
public static void OutputPicture(System.Web.UI.Page page, string encryptedCode)
{
page.Response.Clear();

string key = System.Configuration.ConfigurationSettings.AppSettings["EncryptionKey"];
int digits = Int32.Parse(System.Configuration.ConfigurationSettings.AppSettings["Digits"]);

Bitmap codeBitmap = new Bitmap((digits*16)+20,26, PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(codeBitmap);

g.SmoothingMode = SmoothingMode.HighSpeed;
g.Clear(Color.FromArgb( 0xFF, 0x88, 0x88, 0x88 ));
g.DrawString( Util.DecryptString(encryptedCode,key), new Font("Arial", 16, FontStyle.Bold ), SystemBrushes.WindowText, new Point(10,2));
DrawLines( g, (digits * 16 ) + 20, 26 );
codeBitmap.Save( page.Response.OutputStream, ImageFormat.Jpeg);
g.Dispose();
codeBitmap.Dispose();
page.Response.End();
}

private static void DrawLines( Graphics g, int horMax, int vertMax )
{
Random r = new Random( ( int )DateTime.Now.Ticks << 5 );
for( int i = 0; i < 8; i++ )
{
Point p1 = new Point( r.Next( horMax ), r.Next( vertMax ) );
Point p2 = new Point( r.Next( horMax ), r.Next( horMax ) );
g.DrawLine( new Pen( Color.Black, 2F ), p1, p2 );
}
}


GeneralRe: exception while encoding text and random lines on picture
Cristian Romanescu
8:04 10 Jun '05  
Oops sorry, has been posted previously, I apologize
Generalasp.net w/ vb.net
Metal2000man
6:18 5 May '05  
How do I get this to work w/ vb.net?

What needs to be in the draw image.aspx?


I tried to create a vb version of image.aspx but still get "can't create control" for the code image control.


Generalvisions ebony
Anonymous
14:31 7 Mar '05  
visions ebony
GeneralThanks for the article
Larry Freeman
19:03 31 Jan '05  
I enjoyed this article. 1 thought. I like using both .net and java. I know that people go on with Java is better than .Net or .Net is better than Java. This article is not really an endorsement of the .Net architecture.

Truth is that this algorithm works well in both. It is pretty easy to translate this to Java. Of course, a person should use the technology appropriate to the job at hand and appropriate to their experience and skill.

Cheers

-Larry
GeneralSession time-out problems, I think...
Danny Alvares
13:56 19 Apr '04  
Hi,

I've been using this control with much pleasure on my login pages but it has one behaviour that is annoying and I couldn't tackle. It seems if you render the login page and let it stay open untouched in the browser for a while, then cancel-out of the page you get this ugly red asp.net error. Alas I don't have the error at hand right now but my guess is something goes wrong with the session(state).

Did anybody else encounter this and found a solution?

Thanks,
Danny
Weengineer.nl
GeneralRe: Session time-out problems, I think...
lnarvaez
14:57 16 Jun '04  
I got the same error message, but i fixed it up with just changing Onload event:

protected override void OnLoad(System.EventArgs e)
{
if (!this.Page.IsPostBack)
{
NewCode();
}
else
{
if(Page.Session["SecretCode"]!=null)
{
_code = Page.Session["SecretCode"].ToString();
}
else
{
NewCode();
}
}
}


Leo
GeneralRe: Session time-out problems, I think...
Danny Alvares
2:32 18 Jun '04  
Thanks Leo. But I am still curious what Paul Ingles has to say about this. Paul? Any thoughts/updates?

Danny
GeneralUnavailability of demo
Paul Ingles
13:03 8 Feb '04  
Sorry to anybody trying to see the control working in action, due to a mix-up between me and my hosting company (in my opinion more their fault than mine Poke tongue ) my domain wasn't renewed and my site is now down.

Anyway, I'm using this to spur myself into a bit of action so I'm moving hosts and I'll be writing updated articles for all of my stuff that I've been meaning to do.

Sorry for the inconvenience, but in return you'll get some updated material to get your teeth into Smile

I should also mention the primary domain will be oobaloo.com for both the website and email, but I'll post a message once all the domain name stuff has been sorted. Thanks again.

--
Paul
"Put the key of despair into the lock of apathy. Turn the knob of mediocrity slowly and open the gates of despondency - welcome to a day in the average office." - David Brent, from "The Office"
MS Messenger: paul@oobaloo.co.uk Download my PGP public key
GeneralValidation doesn't work
Danny Alvares
12:25 24 Dec '03  
I get the impression, after testing this control twice this year, that this control doesn't work as expected when there are other textboxes with regular validators on the page. I try to use this functionality in my login page, which has two textboxes (account, password) and their accompanying validators. These validators keep working as expected, but the whole image validator is totally skipped!

What does someone have to do to let this control operate as a regular validator along with other validators and a validationsummary on a page? It is nice in itself but useless if it cannot be slipstreamed into other functionality....

Thanks,

Danny Alvares
Webengineer.nl
GeneralRe: Validation doesn't work
Richard Hill
5:03 6 Jan '04  
This may answer your question, I'm not sure. The control does work as designed. I have done quite a lot of work with the base code given here extending it slightly and can verify that it works. The problem I think you are experiencing is one of client versus server side validation. Regualar validators will be caught client side, the validation they do is then duplicated server side to stop people posting stuff directly to your page bypassing the client side validation. This control cannot be validated client side by its very nature. Therefore only server side validation occurs. If you are expecting a message box to be displayed as opposed to the validation summary being displayed in your HTML then this cannot happen using the standard .NET framework, although with a little additional script it can be achieved. You must also ensure you perform a check server side that the page is valid before assuming it is by calling this.IsValid behind your page submission handler.

hope that helps.

Richard.Eek!
GeneralRe: Validation doesn't work
Danny Alvares
11:51 8 Jan '04  
Thanks Richard. I have this thingy working now and it is marvelous. For all I know the only change I made was adding Page.IsValid to the login button onclick event handler and then it sparkled Wink The control works very well along with the ValidationSummary too.

Danny Alvares
Webengineer.nl
GeneralOutstanding
Silmar01
13:29 30 Jul '03  
Just what I have been looking for the last few weeksBig Grin
GeneralAnti Auto Problem
webmaster@co.sutter.ca.us
13:02 30 Jul '03  
We are getting broken images when testing the code. Upon further investigation the DrawImage.aspx page is producing the following error message:

Server Error in '/' Application.
--------------------------------------------------------------------------------

Invalid length for a Base-64 char array.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.FormatException: Invalid length for a Base-64 char array.

Source Error:


Line 4:        {
Line 5:                  // Draw the output
Line 6:                  Etier.AntiAuto.PictureGenerator.OutputPicture( this, HttpUtility.UrlDecode(Request.QueryString["code"]) );
Line 7:        }
Line 8:   </script>


Source File: C:\Inetpub\ScSutterNet\DrawImage.aspx      Line: 6

Stack Trace:


[FormatException: Invalid length for a Base-64 char array.]
   System.Convert.FromBase64String(String s) +0
   Etier.AntiAuto.Util.DecryptString(String cipherText, String key) +189
   Etier.AntiAuto.PictureGenerator.OutputPicture(Page page, String encryptedCode) +234
   ASP.DrawImage_aspx.Page_Load(Object sender, EventArgs e) in C:\Inetpub\ScSutterNet\DrawImage.aspx:6
   System.Web.UI.Control.OnLoad(EventArgs e) +67
   System.Web.UI.Control.LoadRecursive() +35
   System.Web.UI.Page.ProcessRequestMain() +731




--------------------------------------------------------------------------------
Version Information: Microsoft .NET Framework Version:1.1.4322.573; ASP.NET Version:1.1.4322.573 Eek!
GeneralRe: Anti Auto Problem
ACoolDude
23:51 2 Dec '03  
In DrawImage.aspx replace the line
Etier.AntiAuto.PictureGenerator.OutputPicture( this, HttpUtility.UrlDecode(Request.QueryString["code"]) ); by this line:
Etier.AntiAuto.PictureGenerator.OutputPicture( this, Request.QueryString["code"]); The UrlDecoding is beeing done automatically.

Hope that helps.


Last Updated 8 Nov 2002 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010