Silverlight, with its powerful text and graphics manipulation capabilities and strong interaction with the scripting DOM, seems to be the perfect engine for a Captcha challenge.
Captcha is a challenge-response test used to determine to a degree of confidence that the end user is really human and not a bot. You see Captcha on things like forums, sign up forms, comment posts, and other places that may be susceptible to attacks by scripted bots. Usually, a Captcha involves playing a sound or displaying a distorted image that humans should be able to recognize but would be extremely hard for pattern-matching and/or optical character recognition (OCR) technology to decipher. Because the test is issued by a computer to test for a human, it is often referred to as a reverse-Turing test (a Turing test is designed by humans to test computers).
The key to Captcha is that it makes it difficult, if not impossible, for scripted software to determine the answer to the challenge. Asirra is an example of a Captcha challenge that displays random images of cats and dogs. The user is asked to identify only the cats by clicking on them. While easy for humans, this test is extremely difficult for computer algorithms.
As I was coding the other day, it occurred to me that Silverlight would be perfect for issuing Captcha challenges. It is very easy and straightforward to manipulate text and graphics to obscure the image, and furthermore, the output is NOT a simple bitmap that a robot could parse. Instead, it is an interactive plugin so for a script to recognize the image, it would have to have its own Silverlight engine and be able to scan and recognize what Silverlight renders.
I set out to produce a working example. I purposefully kept to the basics so those of you reading this who are interested have the opportunity to extend and add features.
The first step was to create a simple Captcha challenge class to use.
namespace SilverCaptcha.MVVM
{
[ScriptableType]
public class CaptchaViewModel
{
private const string CAPTCHA_KEY = "SilverCaptcha";
private static readonly char[] _charArray =
"ABCEFGHJKLMNPRSTUVWXYZ2346789".ToCharArray();
public string CaptchaText { get; set; }
public CaptchaViewModel()
{
char[] captcha = new char[8];
Random random = new Random();
for (int x = 0; x < captcha.Length; x++)
{
captcha[x] = _charArray[random.Next(_charArray.Length)];
}
CaptchaText = new string(captcha);
HtmlPage.RegisterScriptableObject(CAPTCHA_KEY, this);
}
[ScriptableMember]
public bool IsHuman(string challengeResponse)
{
return challengeResponse.Trim().ToUpper().Equals(CaptchaText);
}
}
}
The class simply generates a random 8-digit sequence of characters. We supply a list of allowed values to avoid some of the common characters like the number one and letter "I" that could be easily mistaken for one or the other. The property CaptchaText
exposes this value. The IsHuman
method is decorated with the ScriptableMember
tag. This makes it available to the HTML DOM so that you can call it directly from JavaScript. To call it, you must register a "key" or handle to the object. This is done in the constructor through the RegisterScriptableObject
call. We are giving it a handle in the JavaScript DOM of "SilverCaptcha." We'll see later how this is used.
Points of Extension
- Add a method to "refresh" the challenge, i.e. generate a new one (hint: to do this, you'll also need to implement
INotifyPropertyChanged
) - Add parameters to control the challenge (length, use of alpha or numeric, etc.)
Next, we'll need to show the challenge. Because I chose to use the Model-View-ViewModel pattern (MVVM), we won't need any code behind for the XAML. Instead, everything will be bound in the XAML itself. The XAML I came up with looks like this:
<UserControl x:Class="SilverCaptcha.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:MVVM="clr-namespace:SilverCaptcha.MVVM">
<UserControl.Resources>
<MVVM:CaptchaViewModel x:Key="CaptchaVM"/>
</UserControl.Resources>
<Grid Width="100" Height="25" Margin="2" DataContext="{StaticResource CaptchaVM}">
<Grid.Background>
<LinearGradientBrush x:Name="CaptchaBackground"
EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="LightBlue" Offset="1" />
<GradientStop Color="LightSalmon" />
</LinearGradientBrush>
</Grid.Background>
<TextBlock FontSize="14" Width="Auto" Height="Auto"
HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding CaptchaText}"
RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<RotateTransform Angle="-5"/>
</TextBlock.RenderTransform>
<TextBlock.Foreground>
<LinearGradientBrush EndPoint="0.5,0"
StartPoint="0.5,1">
<GradientStop Color="Black" Offset="1" />
<GradientStop Color="Gray" />
</LinearGradientBrush>
</TextBlock.Foreground>
</TextBlock>
</Grid>
</UserControl>
This is fairly straightforward. By referring to CaptchaViewModel
in the resources, an instance is created that can then be referenced using the key. I bind the class to the data context of the main grid using {StaticResource CaptchaVM}
. The gradient is used to obscure the image somewhat. Because the class itself is bound to the grid, we can now simply bind the CaptchaText
property to the text block. We also give that a slight gradient to make it more confusing to image scanning software, then rotate it against the background. That's all there is to it!
Points of Extension
- Obviously, you could randomize or parameterize the angle of rotation and other attributes of the text
- A more severe grid may help obscure the challenge but may also make it more difficult for human readers
- Add a refresh button or icon to refresh the challenge for the user
Now, let's use it in a page. The page itself is fairly straightforward: we have a form with a table, and inside the table is a reference to the Silverlight XAP file, a text box for users to enter their response to the challenge, and a button to click. This section looks like this:
<form id="_form1" runat="server" style="height:100%">
<table><tr><td align="center">
<div id="silverlightControlHost">
<object id="silverlightControl" data="data:application/x-silverlight-2,"
type="application/x-silverlight-2" width="110" height="30">
... etc, etc ...
</object></div></td></tr><tr>
<td align="center">Enter The Text You See Above:<br />
<input type="text" maxlength="20" id="txtChallenge" /></td></tr><tr>
<td align="center"><input type="button" onclick="validateCaptcha();
return false;" value=" OK "/></td></tr></table>
</form>
(Click here to see a live example.)
The key here is that we've assigned the Silverlight object an identifier of silverlightControl
. If you use the JavaScript or control method to load the Silverlight, either way you just need a way to point to the object in the DOM for the Silverlight control.
The JavaScript function is then very straightforward. We simply call the method we exposed in the Silverlight class that will compare the response to the challenge. That code looks like this:
function validateCaptcha() {
var silverlightCtrl = document.getElementById("silverlightControl");
var challenge = document.getElementById("txtChallenge").value;
var valid = silverlightCtrl.Content.SilverCaptcha.IsHuman(challenge);
alert(valid ? "You are human!" : "You don't appear to be human, try again!");
}
This is what the final product looks like:
As you can see, calling the Silverlight method is as simple as grabbing the control that hosts Silverlight, going into Content
, then referencing the tag we gave it when we registered it in the Silverlight code. Then we simply call the method with the contents of the text box and it returns either true or false based on whether or not the response matches the challenge.
This is obviously a fast and basic example but it demonstrates just how flexible and powerful Silverlight can be to generate "mini-apps" or controls that you can embed in your existing HTML pages.
CodeProject