ASP.NET Core 5.0 - Is Human with Google reCAPTCHA
This article will describe the implementation of Google reCAPTCHA v3. I will assume you have downloaded the ASP.NET Core 5.0 - Homegrown Analytics Project or created a new ASP.NET Core 5.0 Razor Pages project. See Tutorial: Get started with Razor Pages in ASP.NET Core.
Homegrown Analytics Project and Article Series
The project is feature complete and will increment the version with updates. The details page includes the version change log. This project is designed and organized to allow easy integration with existing projects. The KenHaggerty. Com. SingleUser NuGet package segregates the user logic. SQL scripts to create the schema, tables, and default data are included in the package. Details, screenshots, and related articles can be found at ASP.NET Core 5.0 - Homegrown Analytics Project.
- ASP.NET Core 5.0 - Homegrown Analytics
- ASP.NET Core 5.0 - Analytics Schema and Integration
- ASP.NET Core 5.0 - Cookie Consent and GDPR
- ASP.NET Core 5.0 - Analytic Data Collection
- ASP.NET Core 5.0 - Not Found Errors
- ASP.NET Core 5.0 - Preferred UserAgents
- ASP.NET Core 5.0 - Analytic Dashboard
- ASP.NET Core 5.0 - Online User Count with SignalR
- ASP.NET Core 5.0 - Is Human with Google reCAPTCHA
- ASP.NET Core 5.0 - Log Maintenance with Hangfire
Before I implemented Google reCAPTCHA v3 on the contact and register pages, I was getting emails and users created by robots. Since, I haven't noticed any. The project has an option to enable the reCAPTCHA demonstration. The Startup class has static properties for options.
Startup.cs:
// Enables the Google Recaptcha demo. // Requires a registered key and secret set in appsettings.json for the // GoogleRecaptchaKey and GoogleRecaptchaSecret properties. public static bool EnableGoogleRecaptcha { get; } = true;
You must register your site to receive a reCAPTCHA v3 key and secret at Google reCAPTCHA v3 - Register a new site. You set the secret, key, and a pass/fail threshold in appsettings.json. reCAPTCHA v3 returns a score (1.0 is very likely a good interaction, 0.0 is very likely a bot).
appsettings.json:
"GoogleRecaptcha": { "Key": "GoogleRecaptchaKey", "Secret": "GoogleRecaptchaSecret", "ScoreThreshold": "0.5" },
The GoogleRecaptcha Key is required to initialize the process. The project injects the application settings with IConfiguration on the Captcha Result page. The OnGet loads the GoogleRecaptcha Key from settings to a GoogleRecaptchaKey property.
Admin > Analytics > CaptchaResult > OnGet:
var googleRecaptcha = _configuration.GetSection("GoogleRecaptcha").GetChildren(); foreach (var item in googleRecaptcha) { if (item.Key == "Key") { GoogleRecaptchaKey = item.Value; break; } }
JavaScript employs the Model. GoogleRecaptchaKey to acquire a challenge token. The token is passed to a Challenge method on the server.
Admin > Analytics > CaptchaResult.cshtml:
<script src="https://www.google.com/recaptcha/api.js?render=@Model.GoogleRecaptchaKey"></script> <script> grecaptcha.ready(function () { grecaptcha.execute('@Model.GoogleRecaptchaKey', { action: 'Contact' }) .then(function (token) { return fetch('/admin/analytics/captcharesult/challenge?token=' + token, { method: 'get', credentials: 'same-origin', headers: { 'Content-Type': 'application/json;charset=UTF-8' } }) .then(function (response) { if (response.ok) return response.text(); else throw Error('Response Not OK'); }) .then(function (text) { try { return JSON.parse(text); } catch (err) { throw Error('Method Not Found'); } }) .then(function (responseJSON) { if (responseJSON.Human) window.location.href = window.location.protocol + "//" + window.location.host + window.location.pathname + '?human=true'; else { document.querySelector('.spinner-div').classList.add('d-none'); document.querySelector('.error-div').classList.remove('d-none'); document.querySelector('.captcha-result').innerHTML = JSON.stringify(responseJSON, null, 2); } }) .catch(function (error) { document.querySelector('.spinner-div').classList.add('d-none'); document.querySelector('.error-div').classList.remove('d-none'); document.querySelector('.captcha-result').innerHTML = error; }) }); }); </script>
The project implements a named System. Net. Http. HttpClient.
Startup > ConfigureServices:
if (EnableGoogleRecaptcha) { services.AddHttpClient("googleRecaptcha", client => { client.BaseAddress = new Uri("https://www.google.com/recaptcha/api/siteverify"); client.DefaultRequestVersion = new Version(2, 0); }); }
The Challenge function parses the configuration settings for the GoogleRecaptcha Secret and ScoreThreshold. The injected IHttpClientFactory initializes the googleRecaptcha client. The secret and token are required by the API to request a response. The score is parsed from the response and compared to the ScoreThreshold. The Challenge function returns a JsonResult indicating the challenge status and a bool Human property.
Admin > Analytics > CaptchaResult.cshtml.cs:
public async Task<JsonResult> OnGetChallengeAsync(string token) { var googleRecaptcha = _configuration.GetSection("GoogleRecaptcha").GetChildren(); string secret = string.Empty; decimal scoreThreshold = 0; //scoreThreshold = 0.5M; foreach (var item in googleRecaptcha) { if (item.Key == "Key") continue; else if (item.Key == "Secret") secret = item.Value; else if (item.Key == "ScoreThreshold") scoreThreshold = decimal.Parse(item.Value); else return new JsonResult(new { Status = "Failed", Errors = "GoogleRecaptcha configuration error." }); } var client = _clientFactory.CreateClient("googleRecaptcha"); var response = await client.GetStringAsync($"?secret={secret}&response={token}").ConfigureAwait(false); var tokenResponse = JsonSerializer.Deserialize<CaptchaTokenResponse>(response); var human = tokenResponse.Score > scoreThreshold; // Test view with human = false; var captchaChallenge = new CaptchaChallenge(tokenResponse, human); var result = await _analyticsService.AddCaptchaChallengeAsync(captchaChallenge).ConfigureAwait(false); if (!result.Succeeded) return new JsonResult(new { Status = "Failed", Errors = $"An error occurred adding a CaptchaChallenge ({result})." }); if (captchaChallenge.Id == 0) return new JsonResult(new { Status = "Failed", Errors = "An error occurred adding a CaptchaChallenge (captchaChallenge.Id = 0)." }); if (!captchaChallenge.Success) return new JsonResult(new { Status = "Failed", Errors = "An error occurred adding a CaptchaChallenge (captchaChallenge.Success = false)." }); return new JsonResult(captchaChallenge); }
The response is deserialized using System. Text. Json into a CaptchaTokenResponse model.
Entities > CaptchaTokenResponse.cs:
public class CaptchaTokenResponse { [JsonPropertyName("success")] public bool Success { get; set; } [JsonPropertyName("score")] public decimal Score { get; set; } [JsonPropertyName("action")] public string Action { get; set; } [JsonPropertyName("challenge_ts")] public DateTime ChallengeDate { get; set; } [JsonPropertyName("hostname")] public string HostName { get; set; } [JsonPropertyName("error-codes")] public List<string> ErrorCodes { get; set; } }
The CaptchaTokenResponse and a human parameter create a CaptchaChallenge entity. The project implements a Captcha Challenges listing page.
The project demonstrates Google reCAPTCHA v3 with a mock contact form.
Comments(0)