Tuesday, January 31, 2012

User friendly CAPTCHA for Asp.Net MVC


In this post I will show you how to add CAPTCHA functionality to a html form in an Asp.Net MVC 4 project. My goal is to make the CAPTCHA problem easy enough for all to solve, like a simple sum operation, and easier to read then the standard CAPTCHA text. An easy to read image is more vulnerable to smart bots that have an ORC system but I prefer to scare less clients then to provide the strongest anti-bot protection. And one more feature, when clicked the image should, change giving the users a new chance to respond correctly.

Implementing CAPTCHA in C# and MVC 4 takes these steps:
  • Create an Action that returns a CAPTCHA image and stores in the user session the right answer
  • Add to your Model a string property named Captcha
  • Add to your View the textbox for Captcha and the image placeholder
  • Validate answer inside your own Action

Render CAPTCHA image
CaptchaController.cs
        public ActionResult CaptchaImage(string prefix, bool noisy = true)
        {
            var rand = new Random((int)DateTime.Now.Ticks);
            //generate new question
            int a = rand.Next(10, 99);
            int b = rand.Next(0, 9);
            var captcha = string.Format("{0} + {1} = ?", a, b);
            //store answer
            Session["Captcha" + prefix] = a + b;
            //image stream
            FileContentResult img = null;
            using (var mem = new MemoryStream())
            using (var bmp = new Bitmap(130, 30))
            using (var gfx = Graphics.FromImage((Image)bmp))
            {
                gfx.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
                gfx.SmoothingMode = SmoothingMode.AntiAlias;
                gfx.FillRectangle(Brushes.White, new Rectangle(0, 0, bmp.Width, bmp.Height));
                //add noise
                if (noisy)
                {
                    int i, r, x, y;
                    var pen = new Pen(Color.Yellow);
                    for (i = 1; i < 10; i++)
                    {
                        pen.Color = Color.FromArgb(
                        (rand.Next(0, 255)),
                        (rand.Next(0, 255)),
                        (rand.Next(0, 255)));
                        r = rand.Next(0, (130 / 3));
                        x = rand.Next(0, 130);
                        y = rand.Next(0, 30);
                        gfx.DrawEllipse(pen, x – r, y – r, r, r);
                    }
                }
                //add question
                gfx.DrawString(captcha, new Font("Tahoma", 15), Brushes.Gray, 2, 3);
                //render as Jpeg
                bmp.Save(mem, System.Drawing.Imaging.ImageFormat.Jpeg);
                img = this.File(mem.GetBuffer(), "image/Jpeg");
            }
            return img;
        }
If you want to use multiple  CAPTCHAs you can use the prefix to store the answer for each form. Much can be improved regarding the rendered image, for example I could use different font and size for each number in the equation, replace the noise with text distortion.
Include  CAPTCHA validator in Model and View
Models.cs
    public class SubscribeModel
    {
        //model specific fields
        [Required]
        [Display(Name = "How much is")]
        public string Captcha { get; set; }
    }
In the View, beside a label, textbox and validator span you’ll need to add an image placeholder for the CAPTCHA.
Index.cshtml
@*form specific fields*@
<div class="editor-label">
    @Html.LabelFor(model => model.Captcha)
    <a href="@Url.Action("Index")">
        <img alt="Captcha" src="@Url.Action("CaptchaImage")" style="" />
    </a>
</div>
<div class="editor-field">
    @Html.EditorFor(model => model.Captcha)
    @Html.ValidationMessageFor(model => model.Captcha)
</div>
Validate CAPTCHA on the server side
Inside your post action where the form submits you can validate the answer by comparing with the session value.
CaptchaController.cs
[HttpPost]
public ActionResult Index(SubscribeModel model)
{
    //validate captcha
    if (Session["Captcha"] == null || Session["Captcha"].ToString() != model.Captcha)
    {
        ModelState.AddModelError("Captcha", "Wrong value of sum, please try again.");
        //dispay error and generate a new captcha
        return View(model);
    }
    return RedirectToAction("ThankYouPage");
}
Instead of validating the CAPTCHA on post, you can use an Ajax call to validate and refresh the image asynchronous, more details on how you can send forms data over Ajax read this blog post.
Prerequisites & Download
In order to easily install all the prerequisites you can use the Web Platform Installer version 4.
download source code files

No comments:

Find a cool free stuff everyday

Giveaway of the Day

Hiren Bharadwa's Posts

DotNetJalps