Last Updated on January 27, 2022 by Aram
In this tutorial, we will learn how to add Google reCAPTCHA v3 to a signup page, the trigger for reCAPTCHA v3 will be from the submit button event, the reCAPTCHA v3 will not interact with the user to keep the implementation seamless, and within the submit data we will send the reCAPTCHA v3 token to let the backend server do the verification for the reCAPTCHA v3 response to find whether the action was done by a real human being or a bot.
reCAPTCHA v3 relies on scoring point between 0.0 and 1.0; The score closer to 1.0 means more trusted user and less possibility of being a bot or fraudster.
CAPTCHA stands for Completely Automated Public Turing Test To Tell Computers and Humans Apart, therefore It is an effective and reliable preventive measure that you can take to prevent bots or fraudsters from spamming or abusing your website, by providing either challenges like puzzles or image detection questions that can only be solved by humans, or in the case of Google reCAPTCHA v3, it wouldn’t require any action to be taken by the requester, however the Google reCAPTCHA v3 behind the scenes algorithms are smart enough to detect and tell whether the interaction is done by a human or a bot.
The server will be implemented as an API endpoint using ASP.NET Core Web API using C# 10 and .NET 6.
First thing is that we need to setup a Google reCAPTCHA account to register a key and decide which reCAPTCHA version are we going to use.
Google reCAPTCHA Account Setup
First, open Google reCAPTCHA link to register for a key and then enter a Label, then choose the reCAPTCHA type (in this tutorial we will implement Google reCAPTCHA v3) , however you can feel free to try out v2 , since both are still supported by Google and will continue to be supported at least for the near future.
Next, if you already have a registered domain name and hosted, then you can enter your domain name. Otherwise, type localhost.
To learn more about type of reCAPTCHA check this link from the official documentation.
Once you are done with filling all the needed details, click submit to create your account.
You will get your Site Key to connect your site with reCAPTCHA v3 (client side integration) and Secret Key to connect your backend with reCAPTCHA to do the server verification.
So with the reCAPTCHA v3 account ready, let’s head over to create an HTML signup page integrated with reCAPTCHA
Choose whatever html editing IDE or app you prefer, for this tutorial I will be using Visual Studio 2022 to create the html page, host it on local IIS Express and write the API Endpoint that will be used as the server to do the reCAPTCHA server verification:
Visual Studio 2022 – Blank Solution – reCAPTCHA v3
Let’s start by creating a blank solution that will host our 2 projects:
Open Visual Studio 2022 and choose create project, then search for Blank Solution
Give it a name like reCAPTCHA:
Adding UI project to host the signup page with reCATPCHA v3 integration
Now from the solution explorer, right click on the solution and choose add a new project, choose ‘ASP.NET Core Empty’
Then, set your project name as ‘UI’
Next, choose .NET 6 (in this tutorial we will setup the localhost using IIS Express which can be easily configured and launched using Visual Studio). However, if you prefer other options, you can opt to run your html page on localhost through other server hosting tools that come with node.js, PHP or Python.
Once the project is ready, open your program.cs file, add ‘app.UseFileServer()’:
1
2
3
4
5
|
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseFileServer();
app.Run();
|
This allows the ASP.NET Core project to read the static files from the wwwroot folder:
Afterwards, in the solution explorer, add a new Folder with name ‘wwwroot’. This will serve as your root folder to host your website files and pages, and inside the root folder, add a new html file with name ‘index.html’ (index.html is used as the default page when a site is requested by its domain name)
index.html
In our tutorial we will build a simple signup page to demonstrate how we can use the reCAPTCHA v3 to ensure that we receive real user data from the signup form not generated through bots or fraudsters that can spam our system and flood our database with false requests.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
input {
font-size: 20px;
}
</style>
<title>Google reCPATCHA Implementation - Codingsonata.com</title>
</head>
<body>
<h1>Signup Form</h1>
<form id="formSignup" action="" method="post">
<input type="email" id="txtEmail" placeholder="Email Address" /> <br /><br />
<input type="password" id="txtPassword" placeholder="Password" /> <br /><br />
<input type="password" id="txtConfirmPassword" placeholder="Confirm Password" /> <br /><br />
<input type="text" id="txtName" placeholder="Full Name" /> <br /><br />
<input type="hidden" id="hdnGoogleRecaptcha" required><br />
<p>reCAPTCHA Token (Valid for 2 minutes)</p>
<label id="lblValue">---</label><br /><br />
<button type="submit" id="btnSignup">Sign up</button>
<br />
<label id="lblMessage"></label>
</form>
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_RECAPTCHA_API_KEY"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
<script type="text/javascript">
$(document).ready(function () {
$(document).on('click', '#btnSignup', function (e) {
e.preventDefault();
grecaptcha.ready(function () {
grecaptcha.execute('YOUR_RECAPTCHA_API_KEY', { action: 'signup' }).then(function (token) {
$('#hdnGoogleRecaptcha').val(token);
$('#lblValue').html(token);
$.post("https://localhost:7229/api/signup",
{
email: $("#txtEmail").val(),
password: $("#txtPassword").val(),
confirmPassword: $("#txtConfirmPassword").val(),
name: $("#txtName").val(),
recaptchaToken: hdnGoogleRecaptcha.val()
})
.done(function (result, status, xhr) {
$("#lblMessage").html(result)
})
.fail(function (xhr, status, error) {
$("#lblMessage").html("Result: " + status + " " + error + " " + xhr.status + " " + xhr.statusText)
});
});
});
});
});
</script>
</body>
</html>
|
Following to that, press F5 to run this page to see that the form is showing properly and more importantly the reCAPTCHA is displaying on the bottom right corner of the page:
Server API to validate the reCAPTCHA v3 Token:
After creating the UI project with the signup page, add a new project to the solution, choose type ASP.NET Core Web API:
Then give it a name like ‘Api’:
Then choose .NET 6 and press create.
Before we start with the API development, remove the extra classes WeatherForecastController and WeatherForecast.
appsettings.json
We will start by adding a new section with name ‘AppSettings’, this will include any settings that we need for the project, in this tutorial we only need to save the reCAPTCHA Secret Key so it won’t be hardcoded within the code.
1
2
3
4
5
6
7
8
9
10
11
12
|
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AppSettings": {
"RecaptchaSecretKey": "YOUR_RECAPTCHA_SECRET_KEY"
}
}
|
AppSettings Entity
Then the AppSettings will be used as the mapping entity for the AppSettings section:
1
2
3
4
5
6
7
|
namespace API
{
public class AppSettings
{
public string RecaptchaSecretKey { get; set; }
}
}
|
Now in the program.cs file, let’s inject the ConfigSection while mapping it to the AppSettings entity, by adding the below line just before the builder.build() method:
1 |
builder.Services.Configure<AppSettings>(builder.Configuration.GetSection(nameof(AppSettings)));
|
Responses
This include the entities that will be used to return the responses back to the client:
ReCaptchaResponse
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
using Newtonsoft.Json;
namespace API.Responses
{
public class ReCaptchaResponse
{
[JsonProperty("success")]
public bool Success { get; set; }
[JsonProperty("score")]
public float Score { get; set; }
[JsonProperty("action")]
public string Action { get; set; }
[JsonProperty("challenge_ts")]
public DateTime ChallengeTs { get; set; } // timestamp of the challenge load (ISO format yyyy-MM-dd'T'HH:mm:ssZZ)
[JsonProperty("hostname")]
public string HostName { get; set; } // the hostname of the site where the reCAPTCHA was solved
[JsonProperty("error-codes")]
public string[] ErrorCodes { get; set; }
}
}
|
SignupResponse
1
2
3
4
5
6
7
8
9
10
11
12
13
|
using System.Text.Json.Serialization;
namespace API.Responses
{
public class SignupResponse
{
public bool Success { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string Error { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string ErrorCode { get; set; }
}
}
|
Requests
In this tutorial, we only have 1 endpoint so we will have 1 request entity as the below:
SignupRequest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
using System.ComponentModel.DataAnnotations;
namespace API.Requests
{
public class SignupRequest
{
[Required]
public string ReCaptchaToken { get; set; }
[EmailAddress]
public string Email { get; set; }
[Required]
public string Password { get; set; }
[Required]
public string ConfirmPassword { get; set; }
[Required]
public string Name { get; set; }
}
}
|
Interfaces
We will have a single interface to handle the signup process:
ISignupService
1
2
3
4
5
6
7
8
9
10
|
using API.Requests;
using API.Responses;
namespace API.Interfaces
{
public interface ISignupService
{
Task<SignupResponse> Signup(SignupRequest signupRequest);
}
}
|
Services
After adding the interface, we need to write the implementation for it, which will include the Google reCAPTCHA server verification http call along with the different validations needed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
using API.Interfaces;
using API.Requests;
using API.Responses;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
namespace API.Services
{
public class SignupService : ISignupService
{
private readonly AppSettings appSettings;
public SignupService(IOptions<AppSettings> appSettings)
{
this.appSettings = appSettings.Value;
}
public async Task<SignupResponse> Signup(SignupRequest signupRequest)
{
var dictionary = new Dictionary<string, string>
{
{ "secret", appSettings.RecaptchaSecretKey },
{ "response", signupRequest.ReCaptchaToken }
};
var postContent = new FormUrlEncodedContent(dictionary);
HttpResponseMessage recaptchaResponse = null;
string stringContent = "";
// Call recaptcha api and validate the token
using (var http = new HttpClient())
{
recaptchaResponse = await http.PostAsync("https://www.google.com/recaptcha/api/siteverify", postContent);
stringContent = await recaptchaResponse.Content.ReadAsStringAsync();
}
if (!recaptchaResponse.IsSuccessStatusCode)
{
return new SignupResponse() { Success = false, Error = "Unable to verify recaptcha token", ErrorCode = "S03" };
}
if (string.IsNullOrEmpty(stringContent))
{
return new SignupResponse() { Success = false, Error = "Invalid reCAPTCHA verification response", ErrorCode = "S04" };
}
var googleReCaptchaResponse = JsonConvert.DeserializeObject<ReCaptchaResponse>(stringContent);
if (!googleReCaptchaResponse.Success)
{
var errors = string.Join(",", googleReCaptchaResponse.ErrorCodes);
return new SignupResponse() { Success = false, Error = errors, ErrorCode = "S05" };
}
if (!googleReCaptchaResponse.Action.Equals("signup", StringComparison.OrdinalIgnoreCase))
{
// This is important just to verify that the exact action has been performed from the UI
return new SignupResponse() { Success = false, Error = "Invalid action", ErrorCode = "S06" };
}
// Captcha was success , let's check the score, in our case, for example, anything less than 0.5 is considered as a bot user which we would not allow ...
// the passing score might be higher or lower according to the sensitivity of your action
if (googleReCaptchaResponse.Score < 0.5)
{
return new SignupResponse() { Success = false, Error = "This is a potential bot. Signup request rejected", ErrorCode = "S07" };
}
//TODO: Continue with doing the actual signup process, since now we know the request was done by potentially really human
return new SignupResponse() { Success = true };
}
}
}
|
Notice in the constructor we are injecting the AppSettings through the IOptions generic Interface and by getting the Value of the IOptions<AppSettings> , we can access the AppSettings properties and use the reCAPTCHA Secret Key.
Also, make sure to install Netwtonsoft.json to be able to use the JsonConvert Class:
Before testing our API, we need to configure the dependency Inject for the SignupService with the ISignupService Interface:
Open the program.cs file again and before the builder.build() method add the below:
1 |
builder.Services.AddTransient<ISignupService, SignupService>();
|
And for the last part of the API, which is the Controller:
SignupController
This will include a single endpoint that will accept the full signup request through POST body which will include the reCAPTCHA token as well.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
using API.Interfaces;
using API.Requests;
using API.Responses;
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class SignupController : ControllerBase
{
private readonly ISignupService signupService;
public SignupController(ISignupService signupService)
{
this.signupService = signupService;
}
[HttpPost]
public async Task<IActionResult> Signup(SignupRequest signup)
{
if (signup == null)
{
return BadRequest(new SignupResponse() { Success = false, Error = "Invalid signup request", ErrorCode = "S01" });
}
if (!ModelState.IsValid)
{
var errors = ModelState.Values.SelectMany(x => x.Errors.Select(c => c.ErrorMessage)).ToList();
if (errors.Any())
{
return BadRequest(new SignupResponse
{
Error = $"{string.Join(",", errors)}",
ErrorCode = "S02"
});
}
}
var response = await signupService.Signup(signup);
if (!response.Success)
{
return UnprocessableEntity(response);
}
return Ok(new SignupResponse() { Success = true });
}
}
}
|
Eventually, this is the final look for our solution with the 2 projects:
Finally, run the API project and make sure you see the Swagger UI documentation for your API on the browser:
Testing the API on Postman
After finishing the development and integration with the Google reCAPTCHA v3, open Postman and create a new request.
First the URL will be the localhost URL that is hosting our endpoint, then set the request as POST and in the body choose ‘raw’ and ‘JSON’, next input the below request:
Notice that the reCAPTCHA token is only a random string, will which yield an error in the response with message ‘invalid-input-response’ this is one of the error-codes that are returned from Google reCAPTCHA service:
You can check the Google reCAPTCHA documentation for all possible error codes.
Now let’s go and obtain a reCAPTCHA token from Google reCAPTCHA v3 through our signup webpage:
After obtaining an actual token from Google reCAPTCHA, we can now test it again on postman to see the output result of the API:
After we wait for more than 2 minutes, the token will expire and the verification will return an error, like the below:
Google reCAPTCHA Admin Console
You can access the dashboard and view the analytics for your sites’ Google reCAPTCHA requests through the Google reCAPTCHA admin console. Make sure you are connected to the Google account from where you’ve created your reCAPTCHA account.
This is how the Google reCAPTCHA admin console displays:
Summary
In this tutorial we learned about Google reCAPTCHA v3, and how it is important to prevent spam and fraudulent requests to your system. We started created a Google reCATPCHA account, and then continued by adding a simple signup page with the Google reCAPTCHA v3 integration, running it over localhost through IIS Express and after that, we built an API using ASP.NET Core Web API 6 that accepts the signup request with the reCAPTHCA token and lastly verified it using Google reCAPTCHA server verification URL through an HTTP Client call while verifying all the possible responses.
Eventually, we tested the implementation using postman with different test cases that include an invalid reCAPTCHA token request, a successful reCAPTCHA token request and finally a timed out request. Then we have peeked at the number and distribution of the reCATPCHA requests and scores at the Google reCAPTCHA admin console.
References
You can find the solution code in my Github account.
Moreover, please check the Official Documentation of Google reCAPTCHA v3, to learn more about the integration. And for further reading about Captcha in general and its history, check this Wikipedia article.
In addition, check my other articles with different topics in ASP.NET Core Web API:
- Exploring the Top 5 Features in C# 13
- Top 5 Language Features in C# 12
- Book Review: API Testing and Development with Postman
- Build a Dictionary App using Uno Platform
- Book Review: Tools and Skills for .NET 8
Bonus
Sharing with you one of my favorite pieces that can reach the inner feelings of a person. Such enchanting masterpiece can only be composed by a unique master and prodigy, Johann Sebastian Bach.
Ray Chen is an award-winning exceptional talent and master in playing violin, who also shows captivating facial expressions that bring a magnificent joy to anyone who enjoys his violin tunes and watches him.
Enjoy and stay safe!
Ray Chen Plays – Partita No. 2 in D minor: “Sarabande” by J.S. Bach