Developing Google ReCaptcha 3 HTML Helper In ASP.NET MVC

This solution will demonstrate a straightforward approach in developing HTML Helper that will render the needed resources to make Google ReCaptcha 3 work in ASP.NET MVC.
 
Upon completing this solution, we will be able to,
  • add or reuse Google ReCaptcha 3 HTML Helpers on views and partial views
  • validate form submission without human interaction
  • let the custom attribute "ValidateReCaptcha" do the work for us
Before creating the ASP.NET MVC project, we should already have a Google ReCaptcha 3 tracking account in place. If you don't have one yet, you may sign-in at https://www.google.com/recaptcha/admin.
 
The following steps will walk us through the solution.
  1. Create a new ASP.NET MVC Project in Visual Studio 2017 using .NET Framework 4.6 and use a descriptive name (e.g. ReCaptcha3ASPNetMVCHelper) for your project.

  2. Compile and build your project to ensure that there are no compile-time errors.

  3. Add two new keys in <appSettings> of Web.config file - “reCaptchaSiteKey” and “reCaptchaSecretKey”. Copy and paste the following codes.
    1. <appSettings>  
    2.     <add key="reCaptchaSiteKey" value="site_key" />  
    3.     <add key="reCaptchaSecretKey" value="secret_key" />  
    4. </appSettings>  
  1. In the “Models” folder, add a new class and name it as “ReCaptchaForm.cs”. Copy and paste the following lines of code into that class.
    1. namespace ASPNetMVCWithReCaptcha3.Models  
    2. {  
    3.     public class ReCaptchaForm  
    4.     {  
    5.         public string Message { getset; }  
    6.     }  
    7. }  
  1. Under project, create a new folder “Classes” and add a new class “ReCaptcha.cs”. Copy and paste the following lines of code.
    1. using System;  
    2. using System.Web;  
    3. using System.Collections.Generic;  
    4. using System.Web.Mvc;  
    5. using System.Net.Http;  
    6. using System.Configuration;  
    7.   
    8. namespace ASPNetMVCWithReCaptcha3.Classes  
    9. {  
    10. }  
  1. Inside the namespace "ASPNetMVCWithReCaptcha3.Classes", add the following lines of code.
    • Add a static class that will store the variables needed to render ReCaptcha.
      1. public static class GoogleReCaptchaVariables  
      2. {  
      3.      public static string ReCaptchaSiteKey = ConfigurationManager.AppSettings["reCaptchaSiteKey"]?.ToString() ?? string.Empty;  
      4.      public static string ReCaptchaSecretKey = ConfigurationManager.AppSettings["reCaptchaSecretKey"]?.ToString() ?? string.Empty;  
      5.      public static string InputName = "g-recaptcha-response";  
      6. }  
    • A static helper class will be essential to render hidden input for response token.
      1. public static class ReCaptchaHelper  
      2. {  
      3.      public static IHtmlString ReCaptchaHidden(this HtmlHelper helper)  
      4.      {  
      5.           var mvcHtmlString = new TagBuilder("input")  
      6.           {  
      7.                Attributes =  
      8.                {  
      9.                     new KeyValuePair<stringstring>("type""hidden"),  
      10.                     new KeyValuePair<stringstring>("id", GoogleReCaptchaVariables.InputName),  
      11.                     new KeyValuePair<stringstring>("name", GoogleReCaptchaVariables.InputName)  
      12.                }  
      13.           };  
      14.           string renderedReCaptchaInput = mvcHtmlString.ToString(TagRenderMode.Normal);  
      15.           return MvcHtmlString.Create($"{renderedReCaptchaInput}");  
      16.      }  
      17.   
      18.      public static IHtmlString ReCaptchaJS(this HtmlHelper helper, string useCase = "homepage")  
      19.      {  
      20.           string reCaptchaSiteKey = GoogleReCaptchaVariables.ReCaptchaSiteKey;  
      21.           string reCaptchaApiScript = "<script src='https://www.google.com/recaptcha/api.js?render=" + reCaptchaSiteKey + "'></script>;";  
      22.           string reCaptchaTokenResponseScript = "<script>$('form').submit(function(e) { e.preventDefault(); grecaptcha.ready(function() { grecaptcha.execute('" + reCaptchaSiteKey + "', {action: '" + useCase + "'}).then(function(token) { $('#" + GoogleReCaptchaVariables.InputName + "').val(token); $('form').unbind('submit').submit(); }); }); }); </script>;";  
      23.           return MvcHtmlString.Create($"{reCaptchaApiScript}{reCaptchaTokenResponseScript}");  
      24.      }  
      25. }  
    • Another helper class to render “span” to server as a placeholder for failed ReCaptcha validation message.
      1. public static IHtmlString ReCaptchaValidationMessage(this HtmlHelper helper, string errorText = null)  
      2. {  
      3.      var invalidReCaptchaObj = helper.ViewContext.Controller.TempData["InvalidCaptcha"];  
      4.      var invalidReCaptcha = invalidReCaptchaObj?.ToString();  
      5.      if (string.IsNullOrWhiteSpace(invalidReCaptcha)) return MvcHtmlString.Create("");  
      6.      var buttonTag = new TagBuilder("span")  
      7.      {  
      8.           Attributes = {  
      9.                new KeyValuePair<stringstring>("class""text-danger")  
      10.           },  
      11.           InnerHtml = errorText ?? invalidReCaptcha  
      12.      };  
      13.      return MvcHtmlString.Create(buttonTag.ToString(TagRenderMode.Normal));  
      14. }  
    • The custom attribute “ValidateReCaptchaAttribute” will do most of the background process particularly the communication with the ReCaptcha 3 api and the validation logic. The internal model class “ResponseToken” will be used to store the response data for reference.
      1. public class ValidateReCaptchaAttribute : ActionFilterAttribute  
      2. {  
      3.      public override void OnActionExecuting(ActionExecutingContext filterContext)  
      4.      {  
      5.           string reCaptchaToken = filterContext.HttpContext.Request.Form[GoogleReCaptchaVariables.InputName];  
      6.           string reCaptchaResponse = ReCaptchaVerify(reCaptchaToken);  
      7.           ResponseToken response = new ResponseToken();  
      8.           if (reCaptchaResponse != null)  
      9.           {  
      10.                response = Newtonsoft.Json.JsonConvert.DeserializeObject(reCaptchaResponse);  
      11.           }  
      12.           if (!response.Success)  
      13.           {  
      14.                AddErrorAndRedirectToGetAction(filterContext);  
      15.           }  
      16.           base.OnActionExecuting(filterContext);  
      17.      }  
      18.   
      19.      public string ReCaptchaVerify(string responseToken)  
      20.      {  
      21.           const string apiAddress = "https://www.google.com/recaptcha/api/siteverify";  
      22.           string recaptchaSecretKey = GoogleReCaptchaVariables.ReCaptchaSecretKey;  
      23.           string urlToPost = $"{apiAddress}?secret={recaptchaSecretKey}&response={responseToken}";  
      24.           string responseString = null;  
      25.           using (var httpClient = new HttpClient())  
      26.           {  
      27.                try  
      28.                {  
      29.                    responseString = httpClient.GetStringAsync(urlToPost).Result;  
      30.                }  
      31.                catch  
      32.                {  
      33.                    //Todo: Error handling process goes here  
      34.                }  
      35.            }  
      36.            return responseString;  
      37.       }  
      38.   
      39.       private static void AddErrorAndRedirectToGetAction(ActionExecutingContext filterContext, string message = null)  
      40.       {  
      41.            filterContext.Controller.TempData["InvalidCaptcha"] = message ?? "Invalid Captcha! The form cannot be submitted.";  
      42.            filterContext.Result = new RedirectToRouteResult(filterContext.RouteData.Values);  
      43.       }  
      44.   
      45.       internal class ResponseToken  
      46.       {  
      47.            public bool Success { getset; }  
      48.            public float Score { getset; }  
      49.            public string Action { getset; }  
      50.            public DateTime Challenge_TS { getset; }  
      51.            public string HostName { getset; }  
      52.            public List ErrorCodes { getset; }  
      53.       }  
      54.  }  
    • Create a postback action in the Controller to implement the [ValidateReCaptcha] attribute.
      1. [HttpPost]  
      2. [ValidateAntiForgeryToken]  
      3. [ValidateReCaptcha]  
      4. public ActionResult Index(ReCaptchaForm form)  
      5. {  
      6.      return View(form);  
      7. }  
    • In the View, add the following lines to create the form with a message text area and Submit button.
      1. @model ASPNetMVCWithReCaptcha3.Models.ReCaptchaForm  
      2. @using ASPNetMVCWithReCaptcha3.Classes;  
      3. @{  
      4.     ViewBag.Title = "ReCaptcha Form";  
      5. }  
      6. @using (Html.BeginForm())  
      7. {  
      8.      @Html.AntiForgeryToken()  
      9.      @Html.LabelFor(model => model.Message)  
      10.      @Html.TextAreaFor(model => model.Message, new { @class = "form-control" })  
      11.      @Html.ReCaptchaValidationMessage()  
      12.      @Html.ReCaptchaHidden()  
      13.      @Html.ReCaptchaJS()  
      14.      <button type="submit" class="btn btn-primary">Send Message</button>  
      15. }  
  1. Rebuild the project to ensure it is error-free.

  2. Don't forget to test the form. A successful ReCaptcha response will allow the form to submit. Otherwise, an error message will display below the message text area on the failed response.


Similar Articles