Wednesday, April 22, 2015

No CAPTCHA reCAPTCHA for ASP.NET MVC

This time the implementation of the reCAPTCHA for ASP.NET MVC. We need an HTML helper:

/// <summary>
/// Extension dla recaptcha
/// </summary>
/// <remarks>
/// https://developers.google.com/recaptcha/docs/display
/// </remarks>
public static class CaptchaHtmlHelper
{
    // private and public keys are read from appsettings but
    // feel free to modify it so that is suits your needs
    private static string CaptchaPrivateKey
    {
        get
        {
            return ConfigurationManager.AppSettings["recaptchaPrivate"];
        }
    }
 
    private static string CaptchaPublicKey
    {
        get
        {
            return ConfigurationManager.AppSettings["recaptchaPublic"];
        }
    }
 
    public static MvcHtmlString GenerateCaptcha(
        this HtmlHelper htmlHelper )
    {
        TagBuilder script = new TagBuilder( "script" );
 
        script.MergeAttribute( "src", "https://www.google.com/recaptcha/api.js?hl=pl" );
        script.MergeAttribute( "async", "true" );
        script.MergeAttribute( "defer", "true" );
 
        TagBuilder div = new TagBuilder( "div" );
        div.MergeAttribute( "class", "g-recaptcha" );
        div.MergeAttribute( "data-sitekey", CaptchaPublicKey );
 
        return new MvcHtmlString( 
           script.ToString( TagRenderMode.Normal ) + 
           div.ToString( TagRenderMode.Normal ) );
    }
}

and an auxiliary validation attribute

/// <summary>
/// Captcha validation
/// </summary>
/// <remarks>
/// https://developers.google.com/recaptcha/docs/verify
/// </remarks>
public class CaptchaValidatorAttribute : ActionFilterAttribute
{
    public class APIResponse
    {
        public bool success { get; set; }
        public string[] error_codes { get; set; } 
    }
 
    private const string RESPONSE_FIELD_KEY = "g-recaptcha-response";
 
    public override void OnActionExecuting( ActionExecutingContext filterContext )
    {
        try
        {
            var query = HttpUtility.ParseQueryString( string.Empty );
            query["secret"] = ConfigurationManager.AppSettings["recaptchaPrivate"];
            query["response"] = filterContext.HttpContext.Request.Form[RESPONSE_FIELD_KEY];
 
            HttpClient client = new HttpClient();
            client.BaseAddress = new Uri( "https://www.google.com/" );
 
            // Add an Accept header for JSON format.
            client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue( "application/json" ) );
 
            HttpResponseMessage httpresponse = 
                client.GetAsync( string.Format( "recaptcha/api/siteverify?{0}", query.ToString() ) ).Result;
            var response = httpresponse.Content.ReadAsAsync<APIResponse>().Result;
 
            // this will push the result value into a parameter in our Action  
            filterContext.ActionParameters["captchaValid"] = response.success;
 
            base.OnActionExecuting( filterContext );
        }
        catch ( Exception ex )
        {
            filterContext.ActionParameters["captchaValid"] = false;                
        }
    }
}  

The validation attribute uses the HttpClient, make sure you reference the Microsoft.AspNet.WebApi.Client package.

To use the extension, place it in your view:

<div> 
    @Html.GenerateCaptcha()
</div>

and decorate your action with the validation attribute:

[HttpPost]
[CaptchaValidator]
public ActionResult TheActionName( bool captchaValid )
{
    if ( ModelState.IsValid && captchaValid )
    {
        ...
As you can see the validation attribute is responsible for setting the value of the captchaValid parameter and the value is then passed down to the action decorated with the attribute.

No comments: