User friendly captcha in MVC

While we all agree on the need to have CAPTCHA validation components, most of us hate them.
Most ASP.NET applications use Recaptcha which work quiet well,however, sometimes it’s too good (if you want to call it that) for a human to read.Can you decode this reCapcha?

Imagine how a user would feel if asked to type in one of the words listed on CAPCHA’s shown on this page. Some would say, no big deal, users can regenerate the CAPTCHA if they can’t read or don’t like the challenge words/characters. For those I would highly recommend a web usability book called “Don’t make me think”.

This solution uses ASP.NET MVC framework, for an ASP.NET web form solution, please see Phil Haak’s honeybotcaptcha control.
In this solution, we will utilize MVC’s ActionFilterAtteribute to perform the validation and ensure that only human input is accepted.

Enough talking and let’s see the code.

First, here is our big helper method.

public static HtmlString Captcha(this HtmlHelper htmlHelper)
      {
          string captchainput = string.Format("<input type=\"{0}\" name=\"{1}\" value=\"{2}\" style=\"{3}\"  />", "text", "Captcha", string.Empty, "display :none;  speak: none;");
          return new HtmlString(captchainput);
      }

A few things to notice from this code;

  1. Our input always has an empty value
  2. Display is set to none to hide it from humans “spam bots ignore css”
  3. Speak is set to none to avoid confusing screen readers
  4. The inline style sets both display and speak to none.

Second, we will need to add a filter to instruct the server on how to deal with this Captcha and how to validate it

public class ValidateCaptcha : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);
        var captcha = filterContext.HttpContext.Request.Form["Captcha"];
        if (!string.IsNullOrEmpty(captcha))
            ((Controller)filterContext.Controller).ModelState.AddModelError("", "SPAM SUCKS");
    }
}

Since the solution utilizes an AtionFilter, this code will only run when a control action is marked with

[ValidateCaptcha]

Third, to use our Captcha, we will have to let the view render it like this;

@using (Html.BeginForm("create", "index", FormMethod.Post, null))
{
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>Comment</legend>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.Subject)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Subject)
            @Html.ValidationMessageFor(model => model.Subject)
        </div>
 
        <div class="editor-label">
            @Html.LabelFor(model => model.Message)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Message)
            @Html.ValidationMessageFor(model => model.Message)
        </div>
        @Html.Captcha()
        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>

And finally our ActionResult;

[ValidateCaptcha]
        public ActionResult Create(Comment model)
        {
            if (TryUpdateModel(model))
            {
                return View(model);
            }
            return View("index");
        }

A spam bot will end up seeing this page when the form is submitted.

Comments (1) -

Jim Benstrad 6/22/2012 2:42:36 PM

Excellent job!
Thanks for sharing

Jim

Pingbacks and trackbacks (1)+

Add comment