I’m implementing CAPTCHA in my form submission as per Sanderson’s book Pro ASP.NET MVC Framework.
The view fields are generated with:
<%= Html.Captcha("testCaptcha")%>
<%= Html.TextBox("attemptCaptcha")%>
The VerifyAndExpireSolution helper is not working as his solution is implemented.
I’m adding validation and when it fails I add a ModelState error message and send the user back to the view as stated in the book:
return ModelState.IsValid ? View("Completed", appt) : View();
But, doing so, generates a new GUID which generates new CAPTCHA text.
The problem is, however, that the CAPTCHA hidden field value and the CAPTCHA image url both retain the original GUID. So, you’ll never be able to enter the correct value. You basically only have one shot to get it right.
I’m new to all of this, but it has something to do with the view retaining the values from the first page load.
Captcha is generated with:
public static string Captcha(this HtmlHelper html, string name)
{
// Pick a GUID to represent this challenge
string challengeGuid = Guid.NewGuid().ToString();
// Generate and store a random solution text
var session = html.ViewContext.HttpContext.Session;
session[SessionKeyPrefix + challengeGuid] = MakeRandomSolution();
// Render an <IMG> tag for the distorted text,
// plus a hidden field to contain the challenge GUID
var urlHelper = new UrlHelper(html.ViewContext.RequestContext);
string url = urlHelper.Action("Render", "CaptchaImage", new{challengeGuid});
return string.Format(ImgFormat, url) + html.Hidden(name, challengeGuid);
}
And then I try to validate it with:
public static bool VerifyAndExpireSolution(HttpContextBase context,
string challengeGuid,
string attemptedSolution)
{
// Immediately remove the solution from Session to prevent replay attacks
string solution = (string)context.Session[SessionKeyPrefix + challengeGuid];
context.Session.Remove(SessionKeyPrefix + challengeGuid);
return ((solution != null) && (attemptedSolution == solution));
}
What about re-building the target field names with the guid? Then, each field is unique and won’t retain the previous form generations’ value?
Or do I just need a different CAPTCHA implementation?
So, I decided to implement reCaptcha. And I’ve customized my view likewise:
This creates two captchas- one in my image container, and another created by the script. So, I added css to hide the auto-generated one:
Then, in my controller, I merely have to test for captchaValid:
And all of this assumes that you’ve implemented the Action Filter attribute and the view helper as detailed at recaptcha.net:
html helper:
Hope this helps someone who got stuck with the implementation in the book.