Using Google reCAPTCHA v2 in ASP.NET Core Web Application

Validating data with Google reCAPTCHA v2 in ASP.NET Core using custom validation attribute

Note

This article has been updated along with the supporting GitHub project. Please visit http://bit.ly/2H1rhLQ for more details and for code reference please use no-robot branch of the GitHub repository https://github.com/dejanstojanovic/Google-reCAPTCHA-ASP.NET-Core/tree/not-robot

In case you have any page on your website which allows anonymous users to POST data to your web application, you should always use any kind of bot protection. Usually that is any kind of CAPTCHA implementation. Even I use the old custom .NET CAPTCHA implementation for the feedback page on this website. Otherwise you are having a potential entry point for crashing your web application.

Recaptcha Animated 3

Google made it easier and more convenient for the users in terms of interaction. It uses single click to detect whether your visitor as a human or a bot. It does this by analyzing behavior of the visit.

How to set it up

What you need to do first is to register your website at Google reCAPTCHA admin page https://www.google.com/recaptcha/admin.

Note

You will need to login with your Google account before you can register your website with Google reCAPTCHA. If you do not have one, you can do so at https://accounts.google.com/signup

Once you are logged in, you can register new website with multiple domains. I always put localhost as one of the domains so I can easily test it from my local machine while I am debugging.

For the type of reCAPTCHA use reCAPTCHA v2. This will enable "I am not a robot" reCAPTCHA style for the website.

Recaptcha Step1

After registering your website for Google reCAPTCHA, you will get the details page on how to integrate Google reCAPTCHA on your website including both client and server side settings.

Recaptcha Step2

Client side setup is pretty much straight forward and easy to setup. You just need to reference the reCAPTCHA script in the head of your page and add the snippet generated for you with the SiteKey. Regarding the SecretKey, it is used for the server side part of the validation and it should be protected, not accessible by the public or anyone else except your application. It should be typically stored in your application config file.

How does it work

To proceed to the server side implementation, I made a small diagram to explain the steps which are performed in order to have the full flow of reCAPTCHA validation executed by your application.

Once user click on reCAPTCHA to confirm he is not a bot, client side control will make the request to Google validation server with a site key provided as an attribute of the tag on your page. Once it gets the response from the validation server it will add the hidden field to the form in which it resides in HTML DOM with validation server response token. This field will have name g-recaptcha-response and will be passed to your application with rest of the form data.

Google Recaptcha V2

Once your request along with g-recaptcha-response value, your server side should validate it by calling Google reCAPTCHA validation url with both response token from the hidden fields and SecretKey. Response from validation server will be sent back to your end in a JSON format. Once you get the 200 OK response from the validation endpoint you can parse it and take out the value of success property of the JSON response. Based on this you can decide whetehr the request was from a real person or a bot and based on that take action.

Validation response will have the following structure.

{
  "success": true,
  "challenge_ts": "2018-05-24T15:37:21Z",
  "hostname": "localhost"
}
    

ASP.NET Core implementation

In ASP.NET Web application I decided to make reCAPTCHA validation as part of my Model validation because it is part of the model eventually, so makes sense to do it on a model validation. Before I dig into the implementation of Custom Validation Attribute, let's first setup some basic things.

As I mentioned before we are going to need the SecretKey in our application config, so we first need to add the appsettings.json and add the SecretKey to it

{
  "GoogleReCaptcha": {
    "ClientKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "SecretKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  }
}
    

Second thing we need to do is to setup the dependency injection for our configuration so it becomes available through out the whole project, so we need to update out Startup.cs class file

using System.IO;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Sample.Core.GoogleReCaptcha.Web
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940

        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration, IHostingEnvironment env)
        {
            Configuration = configuration;
         
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IConfiguration>(new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile($"appsettings.json")
                .Build());

            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

    

Now we can finally focus on custom validation attribute which will do the validation process for our model. 

using Microsoft.Extensions.Configuration;
using Newtonsoft.Json.Linq;
using System;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Net.Http;

namespace Sample.Core.GoogleReCaptcha.Web.Validation
{

    public class GoogleReCaptchaValidationAttribute : ValidationAttribute
    {
       
        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            Lazy<ValidationResult> errorResult = new Lazy<ValidationResult>(() => new ValidationResult("Google reCAPTCHA validation failed", new String[] { validationContext.MemberName }));

            if (value == null || String.IsNullOrWhiteSpace( value.ToString())) 
            {
                return errorResult.Value;
            }

            IConfiguration configuration = (IConfiguration)validationContext.GetService(typeof(IConfiguration));
            String reCaptchResponse = value.ToString();
            String reCaptchaSecret = configuration.GetValue<String>("GoogleReCaptcha:SecretKey");
            

            HttpClient httpClient = new HttpClient();
            var httpResponse = httpClient.GetAsync($"https://www.google.com/recaptcha/api/siteverify?secret={reCaptchaSecret}&response={reCaptchResponse}").Result;
            if (httpResponse.StatusCode != HttpStatusCode.OK)
            {
                return errorResult.Value;
            }

            String jsonResponse = httpResponse.Content.ReadAsStringAsync().Result;
            dynamic jsonData = JObject.Parse(jsonResponse);
            if (jsonData.success != true.ToString().ToLower())
            {
                return errorResult.Value;
            }

            return ValidationResult.Success;

        }
    }
}

    

There are few tricks I had to do in order to make the custom validation attribute work and to be easy to modify. First thing is using Lazy keyword for invalid validation result. Since there are multiple occasions where model is not valid, we would have to generate the same response multiple times which would cause code repetition, so I decided to use Lazy class to make the invalid response instance only in case it is really needed. For successful response, for example, invalid ValidationResult instance would never be initiated.

Second, more important trick is accessing the configuration injected instance from the ValidationAttribute class instance. Unlike Controllers, you cannot use parameterized constructor, so you need to take it from the ValidationContext

IConfiguration configuration = (IConfiguration)validationContext.GetService(typeof(IConfiguration));
    

Now, once we have our main class created, we can switch to our model. Since we want to make this re-usable, it makes sense to have validation enabled model as an abstract class which we can inherit to a model we want to apply reCAPTCHA validation to. So firts thing will be to create out abstract model base

using Microsoft.AspNetCore.Mvc;
using System;
using System.ComponentModel.DataAnnotations;

namespace Sample.Core.GoogleReCaptcha.Web.Validation
{
    public abstract class GoogleReCaptchaModelBase
    {
        [Required]
        [GoogleReCaptchaValidation]
        [BindProperty(Name = "g-recaptcha-response")]
        public String GoogleReCaptchaResponse { get; set; }
    }
}

    

Now that we have our custom validation attribute applied to abstract class, we can just inherit it byt the model where we vant the validation to be applied to

using System;
using System.ComponentModel.DataAnnotations;
using Sample.Core.GoogleReCaptcha.Web.Validation;
using Newtonsoft.Json;

namespace Sample.Core.GoogleReCaptcha.Web.Models
{
    public class Comment : GoogleReCaptchaModelBase
    {
        [Required]
        public String Title { get; set; }

        [Required]
        public String Content { get; set; }
    }
}

    

Once we get response on the client side from the Google validation server, we will have the hidden filed HTML Element named g-recaptcha-response added to the FORM tag of out document DOM. Since you cannot have the property of this name in your model, I used BindProperty attribute to bind this field to meaningful property called GoogleReCaptchaResponse. 

I also declared GoogleReCaptchaValidation attribute for this field so that validation is triggered automatically on the model binding. The rest is pretty much straigh forward.

We need the controller to route our requests

using Microsoft.AspNetCore.Mvc;
using Sample.Core.GoogleReCaptcha.Web.Models;

namespace Sample.Core.GoogleReCaptcha.Web.Controllers
{
    public class CommentsController : Controller
    {
        [HttpGet]
        public IActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public IActionResult Index(Comment comment)
        {
            if (!ModelState.IsValid)
            {
                //TODO: Comment saving logic here
                return View();
            }
            return View();
        }
    }
}
    

And we are going to need a view to render out the input and messages after the successful/failed submit of the data.

Since we have our Google reCAPTCHA keys stored in config, to access them in the razor view we need to inject them, but since the view is not a class and we do not have constructor where we can inject it, we are going to use @inject directive

@using Microsoft.Extensions.Configuration
@inject IConfiguration Configuration
    

This will allow us to access configuration values in the view and get our site key.

    @using (Html.BeginForm(method: FormMethod.Post, controllerName: "Comments", actionName: "Index", htmlAttributes: new { id = "comment-form" }))
    {
        <div>
            <label for="Title">Title</label>
            <input type="text" name="Title" id="Title" />
        </div>
        <div>
            <label for="Content">Content</label>
            <textarea type="text" name="Content" id="Content" rows="8" cols="20"></textarea>
        </div>
        <div>
            <div class="g-recaptcha" data-sitekey="@(Configuration.GetValue<String>("GoogleReCaptcha:ClientKey"))"></div>
        </div>
        <div>
            <button type="submit">Send comment</button>
        </div>
        @if (!ViewData.ModelState.IsValid)
        {
            <ul>
                @foreach (var modelState in ViewData.ModelState.Values)
                {
                    foreach (var error in modelState.Errors)
                    {
                        <li>@error.ErrorMessage</li>
                    }
                }
            </ul>
        }
    }
    

This is just a portion of the whole razor view which is responsible for rendering logic. To get the whole view you can check the repository https://github.com/dejanstojanovic/Google-reCAPTCHA-ASP.NET-Core on GitHub.

Since we are making everything re-usable , you may notice that out HTML snippet in razor view pretty much has to be repeated in every view we want reCAPTCHA to appear on. Usually this is only one, for example registration page but this may vary depending on the project requirements.

Another thing is customization of reCAPTCHA. Google isn't pretty much flexible in giving you freedom to customize it's CAPTCHA, but still there may be some customizations you can read about at https://developers.google.com/recaptcha/docs/display or https://developers.google.com/recaptcha/docs/faq. This is one more reason you do not want to duplicate your HTML snipet you use in razor view.

Note

One of the commonly used customization is data-callback attribute. This is value which contains the name of JavaScript function you want to invoke after validation. It is useful in case you want to enable submit button upon validation on client side. You can read more on https://developers.google.com/recaptcha/docs/display

For this purpose you can always switch to HtmlHelpers, concept probably familiar from previous MVC versions. It is changed a bit in Core, but idea is the same. So here is one of the possible implementations for the Google reCAPTHCA HtmlHelper which you can use to replace the HTML snippet.

using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
using System;
using System.IO;

namespace Sample.Core.GoogleReCaptcha.Web.Validation
{
    public static class GoogleReCaptchaTagHelper
    {
        public static IHtmlContent GoogleReCaptcha(this IHtmlHelper htmlHelper, String siteKey, String callback = null)
        {
            var tagBuilder = new TagBuilder("div");
            tagBuilder.Attributes.Add("class", "g-recaptcha");
            tagBuilder.Attributes.Add("data-sitekey", siteKey);
            if (callback != null && String.IsNullOrWhiteSpace(callback))
            {
                tagBuilder.Attributes.Add("data-callback", callback);
            }
            using (var writer = new StringWriter())
            {
                tagBuilder.WriteTo(writer, System.Text.Encodings.Web.HtmlEncoder.Default);
                var htmlOutput = writer.ToString();
                return htmlHelper.Raw(htmlOutput);
            }
        }
    }
}

    

In previous versions of MVC, you had to register your helper inside Views/Web.config file. This is not the case anymore and as soon as you add using of the custom helper namespace inside the view you can use it

@Html.GoogleReCaptcha(Configuration.GetValue<String>("GoogleReCaptcha:ClientKey"))
    

Automation testing

If you have any automation testing in place for your pages, they will probably fail as Google reCAPTCHA will recognize them as automated clicks and there for it will set models as invalid. For this scenario Google has specified set of SecretKey and SiteKey specially for automation testing:

  • Site key: 6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
  • Secret key: 6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe
Note

These valuse may chnage over time so check them at https://developers.google.com/recaptcha/docs/faq

References

Disclaimer

Purpose of the code contained in snippets or available for download in this article is solely for learning and demo purposes. Author will not be held responsible for any failure or damages caused due to any other usage.


About the author

DEJAN STOJANOVIC

Dejan is a passionate Software Architect/Developer. He is highly experienced in .NET programming platform including ASP.NET MVC and WebApi. He likes working on new technologies and exciting challenging projects

CONNECT WITH DEJAN  Loginlinkedin Logintwitter Logingoogleplus Logingoogleplus

JavaScript

read more

SQL/T-SQL

read more

Umbraco CMS

read more

PowerShell

read more

Comments for this article