blazor_dotnet

Blazor Implementing Google reCAPTCHA v3

Google reCAPTCHA v3 in a Blazor application. Google reCAPTCHA is a service that helps protect websites from spam and abuse by verifying that the user is a human and not a bot. reCAPTCHA v3 is the latest version of this service and provides a seamless user experience without requiring any user interaction.

Site Key: A unique identifier for your website, obtained from the reCAPTCHA admin console. This key is used to identify your website when making API requests to Google’s reCAPTCHA service.

Secret Key: A secret key associated with your site key, also obtained from the reCAPTCHA admin console. This key is used to authenticate your API requests to Google’s reCAPTCHA service.

Token: A token generated by the reCAPTCHA JavaScript API, which is sent to the server for verification. This token contains information about the user’s interaction with your website and is used to determine the likelihood of the user being a bot.

Score: reCAPTCHA v3 returns a score between 0.0 and 1.0, indicating the likelihood that the user is a bot. A score closer to 1.0 indicates a higher likelihood of being a human.

GooglereCAPTCHAv3Service.cs

GooglereCAPTCHAv3Service class demonstrates how to verify a reCAPTCHA token using the Google reCAPTCHA v3 API. Let’s break down the code structure:

Namespace: The code is placed inside the BlazorAppreCAPTCHAv3.Data namespace.

Class: The GooglereCAPTCHAv3Service class contains a single method Verify that takes a reCAPTCHA token as input and returns a GooglereCAPTCHAv3Response object.

HttpClient: The code uses the HttpClient class to send an HTTP POST request to the reCAPTCHA API endpoint.

FormUrlEncodedContent: The reCAPTCHA API requires the secret and response parameters to be sent as form data. The code creates a FormUrlEncodedContent object and adds these parameters to it.

PostAsync: The code sends the HTTP POST request to the reCAPTCHA API endpoint with the form data.

ReadAsStringAsync: The code reads the response from the API as a string.

JsonSerializer: The code deserializes the JSON response string into a GooglereCAPTCHAv3Response object using the JsonSerializer class.

Exception Handling: The code catches any exceptions that occur during the API request and rethrows them.

Return Statement: The code returns the GooglereCAPTCHAv3Response object.

using System.Text.Json;

namespace BlazorAppreCAPTCHAv3.Data;

public class GooglereCAPTCHAv3Service
{
    public virtual async Task<GooglereCAPTCHAv3Response?> Verify(string token)
    {
        GooglereCAPTCHAv3Response? reCaptchaResponse;
        using (var httpClient = new HttpClient())
        {
            var content = new FormUrlEncodedContent(new[] {
                new KeyValuePair<string, string>("secret", GooglereCAPTCHAv3Settings.SecretKey),
                new KeyValuePair<string, string>("response", token)
            });
            try
            {
                var response = await httpClient.PostAsync($"https://www.google.com/recaptcha/api/siteverify", content);
                var jsonString = await response.Content.ReadAsStringAsync();
                reCaptchaResponse = JsonSerializer.Deserialize<GooglereCAPTCHAv3Response>(jsonString);
            }
            catch (Exception)
            {
                throw;
            }

            return reCaptchaResponse;
        }
    }
}
GooglereCAPTCHAv3Settings.cs

To implement Google reCAPTCHA v3 in a Blazor application, we need to create a class that holds the reCAPTCHA settings. In this example, we have a class called GooglereCAPTCHAv3Settings in the BlazorAppreCAPTCHAv3.Data namespace.

In the GooglereCAPTCHAv3Settings class, we have two constant fields: SiteKey and SecretKey. These fields hold the site key and secret key provided by Google reCAPTCHA. You need to replace the placeholders with your actual site key and secret key.

namespace BlazorAppreCAPTCHAv3.Data;

public class GooglereCAPTCHAv3Settings
{
    public const string SiteKey = "YOUR_GOOGLE_reCAPTCHAv3_SITEKEY";
    public const string SecretKey = "YOUR_GOOGLE_reCAPTCHAv3_SECRETKEY";
}
GooglereCAPTCHAv3Response.cs

A model class GooglereCAPTCHAv3Response that represents the response received from the reCAPTCHA API. It has properties like success, score, action, challenge_ts, and hostname. These properties will be populated with the relevant data returned by the reCAPTCHA service.

namespace BlazorAppreCAPTCHAv3.Data;

public class GooglereCAPTCHAv3Response
{
    public bool success { get; set; }
    public double score { get; set; }
    public string action { get; set; }
    public DateTime challenge_ts { get; set; }
    public string hostname { get; set; }
}
_Layout.cshtml
<script src=@($"https://www.google.com/recaptcha/api.js?render={BlazorAppreCAPTCHAv3.Data.GooglereCAPTCHAv3Settings.SiteKey}")></script>
<script>
	runCaptcha = function (actionName) {
		return new Promise((resolve, reject) => {
			grecaptcha.ready(function () {
				grecaptcha.execute('@($"{BlazorAppreCAPTCHAv3.Data.GooglereCAPTCHAv3Settings.SiteKey}")', { action: 'submit' }).then(function (token) {
					resolve(token);
				});
			});
		});
	};
</script>

Let’s break down the code and understand its functionality:

<script src=@($"https://www.google.com/recaptcha/api.js?render={BlazorAppreCAPTCHAv3.Data.GooglereCAPTCHAv3Settings.SiteKey}")></script>

This line of code includes the reCAPTCHA API script from Google’s servers. It dynamically generates the URL based on the SiteKey value provided in the GooglereCAPTCHAv3Settings class.

<script>
	runCaptcha = function (actionName) {
		return new Promise((resolve, reject) => {
			grecaptcha.ready(function () {
				grecaptcha.execute('@($"{BlazorAppreCAPTCHAv3.Data.GooglereCAPTCHAv3Settings.SiteKey}")', { action: 'submit' }).then(function (token) {
					resolve(token);
				});
			});
		});
	};
</script>

This script defines a function called runCaptcha that can be used to execute the reCAPTCHA verification process. It takes an actionName parameter, which represents the specific action you want to protect. Inside the function, it uses the grecaptcha object provided by the reCAPTCHA API to execute the verification process. Once the verification is successful, it resolves the promise with the generated token.

ReCAPTCHAv3.razor
@page "/ReCAPTCHAv3"
@using BlazorAppreCAPTCHAv3.Data;
@using BlazorAppreCAPTCHAv3.ViewModels;

@inject IJSRuntime JSRuntime
@inject GooglereCAPTCHAv3Service GooglereCAPTCHAv3Service

<PageTitle>reCAPTCHAv3</PageTitle>

<EditForm Model="@blogPostViewModel" OnValidSubmit="HandleValidSubmit">
    <DataAnnotationsValidator />
    <ValidationSummary />

    <div class="form-group">
        <label class="control-label">@nameof(BlogPostViewModel.Title)</label>
        <InputText @bind-Value="blogPostViewModel.Title" class="form-control" />
        <ValidationMessage For="@(() => blogPostViewModel.Title)" class="text-danger" />
    </div>

    <div class="form-group">
        <label class="control-label">@nameof(BlogPostViewModel.Content)</label>
        <InputText @bind-Value="blogPostViewModel.Content" class="form-control" />
        <ValidationMessage For="@(() => blogPostViewModel.Content)" class="text-danger" />
    </div>

    <div class="form-group">
        <input id="submitBtn" type="submit" value="Submit" class="btn btn-primary" />
    </div>

</EditForm>


<div class="form-group mt-3">
    @if (googlereCAPTCHAv3Response != null)
    {
        <label class="control-label"><strong>Result</strong></label>
        <div class="form-group">
            <label class="control-label">Status</label>
            <input type="text" class="form-control" value="@googlereCAPTCHAv3Response.success.ToString()" />
        </div>
        <div class="form-group">
            <label class="control-label">Score</label>
            <input type="text" class="form-control" value="@googlereCAPTCHAv3Response.score" />
        </div>
    }

    <label class="control-label">reCAPTCHA token</label>
    <textarea rows="4" cols="50" readonly class="form-control">
        @token
    </textarea>


</div>

@code {

    BlogPostViewModel blogPostViewModel = new BlogPostViewModel();
    string token = "";
    GooglereCAPTCHAv3Response? googlereCAPTCHAv3Response;

    private async Task HandleValidSubmit()
    {
        googlereCAPTCHAv3Response = await GooglereCAPTCHAv3Service.Verify(token);
        StateHasChanged();
    }

    protected override async void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            token = await JSRuntime.InvokeAsync<string>("runCaptcha");
            StateHasChanged();
        }
    }

}

In the ReCAPTCHAv3.razor page:

HandleValidSubmit(): This method is called when the form is submitted and the validation is successful. It calls the Verify() method of the GooglereCAPTCHAv3Service to verify the reCAPTCHA token and assigns the response to the googlereCAPTCHAv3Response object. The StateHasChanged() method is called to update the UI.

OnAfterRender(): This method is called after the component has been rendered. It checks if it is the first render and then calls the runCaptcha JavaScript function using the JSRuntime service to generate the reCAPTCHA token. The StateHasChanged() method is called to update the UI.

Source

Full source code is available at this repository in GitHub:
https://github.com/akifmt/DotNetCoding/tree/main/src/BlazorAppreCAPTCHAv3