meta info

Google reCAPTCHA v3

By Ziya Mollamahmut

How to protect user login with google reCAPTCHA v3 programmatically in Asp.Net Core projects. reCAPTCHA usage is already explained in google docs, but here I will explain how to use it with Asp.Net Core login page.

Basically, we will use reCAPTCHA where we do post from pages like Login that are accessible to anonymous users and bots. So, in this sample I will use Login page for demonstration, but this can be applied to any similar page like reset password and registration pages as well.

Get the keys

Define Settings in Json

Put the site key and secret key in user secrets file:

"GoogleReCAPTCHAv3": {
  "SiteKey": "MY-SITE-KEY",
  "SecretKey": "MY-SECRET-KEY"
}

It is recommended to store passwords and keys in a secure place, but using appsettings.json also can work for testing purpose.

Create reCAPTCHA Service

  • Create a new client service that will connect to the reCAPTCHA API's and do valdiation in the backend, this service will use the SecretKey and Token for validation.
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using System;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace GoogleReCaptchaV3
{
    public class ReCaptchaService
    {
        private readonly HttpClient _client;
        private readonly ILogger _log;

        private readonly string _secretKey;

        public ReCaptchaService(
            HttpClient httpClient,
            ILogger<ReCaptchaService> logger,
            IConfiguration configuration)
        {
            _log = logger;
            _client = httpClient ?? throw new NullReferenceException(nameof(httpClient));
            _client.BaseAddress = new Uri("https://www.google.com");

            _secretKey = configuration.GetValue<string>("GoogleReCAPTCHAv3:SecretKey")
                      ?? throw new NullReferenceException("GoogleReCAPTCHAv3:SecretKey");
        }

        public async Task<bool?> ValidateReCaptchaAsync(string token)
        {
            try
            {
                var response = await _client.GetAsync($"/recaptcha/api/siteverify?secret={_secretKey}&response={token}");

                if (response.StatusCode != HttpStatusCode.OK)
                    return false;

                string JSONresponse = await response.Content.ReadAsStringAsync();
                dynamic JSONdata = JObject.Parse(JSONresponse);

                if (JSONdata.success != "true")
                    return false;
            }
            catch (SocketException e)
            {
                _log.LogCritical("CAN'T CONNECT TO GOOGLE CAPTCHA SERVER!");
                _log.LogError(e.Message);
                return null;
            }
            catch (HttpRequestException e)
            {
                _log.LogCritical("CAN'T CONNECT TO GOOGLE CAPTCHA SERVER!");
                _log.LogError(e.Message);
                return null;
            }

            return true;
        }
    }
}
  • Register the reCAPTCHA service in startup:
services.AddHttpClient<ReCaptchaServics>();

Setup in the page model

In the backend we will get the SiteKey from user secrets file, and then we will validate the token using our newely created service.

[AllowAnonymous]
public class LoginModel : PageModel
{
    private readonly ReCaptchaService _captcha;
    public readonly string SiteKey;

    // ... other services


    // the token will be generated by google reCAPTCHA API js call
    // add as relevant script and filed in the login form
    [FromForm]
    public string Token { get; set; }

    public LoginModel(ReCaptchaService captcha,
                      IConfiguration configuration,
                      // ... other services
                      )
    {
        _captcha = captcha;

        // get SiteKey from user secrets
        SiteKey = configuration.GetValue<string>("GoogleReCAPTCHAv3:SiteKey");
    }

    // Do captcha valdiation before model validiation
    public async Task<IActionResult> OnPostAsync(string returnUrl = null)
    {
        returnUrl ??= Url.Content("~/");

        var captchaChallenge = await _captcha.ValidateReCaptchaAsync(Token);
        if (captchaChallenge == null)
        {
            // reCAPTCHA validiation is not possible
            // could be a connection problem
            _log.LogCritical("Can't connect to reCAPTCHA servers");

            return Page();
        }
        else if (!captchaChallenge.Value)
        {
            _log.LogWarning("You didn't pass the reCAPTCHA challenge!");
            return Page();
        }

        // if we get here, so far we passed the reCAPTCHA challenge :)
        // .. rest of the code
    }
}

Setup in razor page

  • Here we need to add additonal Token field to the login form:
<input type="hidden" asp-for="Token" />

The token value will be generated by google reCAPTCHA API through below js code:

<!-- Google ReCAPTCHA.V3-->
<script src="https://www.google.com/recaptcha/api.js?render=@Model.SiteKey"></script>
<script>
    grecaptcha.ready(function () {
        grecaptcha.execute('@Model.SiteKey', { action: 'homepage' }).then(function (token) {
            document.getElementById("Token").value = token;
        });
    });
</script>

Sample Project

https://github.com/LazZiya/BlogSamples/tree/master/GoogleReCaptchaV3