Tuesday 14 October 2014

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");
}

2 comments:

  1. this is very useful and easy to implement! keep posting. Anti captcha key

    ReplyDelete
  2. Tinted: Prices of Titanium Rocks - iTaniumArts
    Tinted: Prices of Titanium Rocks. Buy womens titanium wedding bands it titanium eyeglass frames online, buy it 출장안마 on the website. titanium welding Price is 0.10 EUR, 30 USD and other currencies are currencies everquest: titanium edition of the

    ReplyDelete

C# LINQ Joins With SQL

There are  Different Types of SQL Joins  which are used to query data from more than one database tables. In this article, you will learn a...