Google reCAPTCHA v3 (App Extension)
This App Extension helps you add Google reCAPTCHA v3 to custom forms in your 2sxc app, so you can reduce spam and automated submissions.
Unlike reCAPTCHA v2, v3 runs in the background and returns a score (0.0-1.0).
Tip
reCAPTCHA v3 is not "a secure checkbox". You must always verify the token server-side, and you should tune your score threshold over time.
How reCaptcha Works
reCAPTCHA v3 works by analyzing user interactions on your website to determine whether the user is a human or a bot. Here's how the process works:
- Your app's server needs to know if a user is likely a bot and decide whether to accept the request.
- To provide an answer, the server requests a score from Google's reCAPTCHA API.
- For this to work, reCAPTCHA must have watched the user behavior in the browser. This requires the browser to load the reCAPTCHA JavaScript library.
Installation
Preparation
First: Get your Google keys (Site Key + Secret). Create a reCAPTCHA v3 site in the Google reCAPTCHA admin console and copy:
- Site key (public, used in the browser)
- Secret key (private, used on the server)
Then Configure the extension in 2sxc App Settings. You can configure everything directly in 2sxc App Settings for this extension:
- Site Key
- Secret Key
- Score Threshold (minimum score required to accept requests)
Code Example
Razor Code containing the HTML Form + reCAPTCHA v3 JS
Before a form can be verified, the browser must:
- Load the reCAPTCHA v3 JavaScript
- Execute an action to generate a token
- Send that token together with the form data to the server
The following Razor view demonstrates the minimal required setup.
@inherits Custom.Hybrid.RazorTyped
@{
// Read site key
var siteKey = Kit.SecureData.Parse(AllSettings.String("GoogleRecaptcha.SiteKey")).Value;
// WebApi Endpoint for DocsFormController, supporting Polymorph editions
var submitUrl = Link.To(api: $"{MyView.Edition}/api/DocsForm/SubmitAsync");
}
@* Load reCAPTCHA script *@
<script src="https://www.google.com/recaptcha/api.js?render=@siteKey"></script>
@* Minimal visible demo with button to submit *@
<button id="send-demo" class="btn btn-primary" type="button">
Test ReCaptcha v3
</button>
@* Script to handle button click, get token, and send to API *@
<script>
(() => {
// On click, execute reCAPTCHA and send token to API
document.getElementById("send-demo")?.addEventListener("click", () => {
grecaptcha.execute("@siteKey", { action: "submit" })
.then(token =>
fetch("@submitUrl", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: "hello", token })
})
)
.then(async r => {
if (!r.ok) throw new Error( await r.text() || "captcha-failed");
})
.then(() => alert("reCAPTCHA accepted"))
.catch(err => alert("reCAPTCHA failed: " + (err?.message || err)));
});
})();
</script>
What happens here:
- The site key is read from App Settings
- reCAPTCHA v3 runs silently in the background
- A token is generated for the action "submit"
- The token is sent to the server together with the form data
WebApi Sample Validating the Token
This code will receive the request,
validate the token using the app extension RecaptchaValidator service,
and decide if the request should be accepted or rejected.
using AppCode.Extensions.GoogleRecaptchaV3;
using System.Threading.Tasks;
using ToSic.Sxc.WebApi;
#if NETCOREAPP
using Microsoft.AspNetCore.Mvc;
#else
using System.Web.Http;
using IActionResult = System.Web.Http.IHttpActionResult;
#endif
/// <summary>
/// Demo API controller to test reCAPTCHA v3 form submissions
/// Used by the frontend test form to verify token + message
/// </summary>
[AllowAnonymous]
public class DocsFormController : Custom.Hybrid.ApiTyped
{
[HttpPost]
[SecureEndpoint]
public async Task<IActionResult> SubmitAsync([FromBody] DemoFormRequest request)
{
// Basic input validation
if (string.IsNullOrWhiteSpace(request?.Message))
return BadRequest("Message-missing");
// Validate reCAPTCHA token - include Client IP as it improves validation accuracy
var result = await GetService<RecaptchaValidator>()
.ValidateAsync(request.Token, remoteIp: Request.GetClientIp());
if (!result.Success)
return BadRequest(result.Error);
// Continue if reCAPTCHA validation succeeded
// YOUR CUSTOM LOGIC goes here (e.g., save the form, send email, etc.)
// Demo success response
return Ok();
}
}
What happens here:
- The token is validated server-side using the
RecaptchaValidatorservice provided by the app extension. - The validator automatically:
- Calls Google's reCAPTCHA verification API using the secret key configured in the app settings.
- Checks if the token is valid and not expired.
- Compares the returned score against the configured score threshold.
- Requests with scores below the threshold are rejected with an error message.
- If validation succeeds, the form data can be processed further (e.g., saving the message).
- If validation fails, a
BadRequestresponse is returned with the error details.
Hiding the reCAPTCHA Badge
By default, reCAPTCHA v3 displays a badge in the bottom-right corner of your page. You can hide it with simple CSS:
.grecaptcha-badge {
visibility: hidden;
}
Warning
Google's Terms of Service Requirement
If you hide the reCAPTCHA badge, you must include the following text visibly in your user flow (e.g., near your form):
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
See the official Google reCAPTCHA FAQ for more details.
History
- Creating v1 ca. 2026-01
Shortlink: https://go.2sxc.org/ext-grecapt3