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()
{
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)
{
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:
- 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
- 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.
- 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.
- 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.