Open Closed

How to Use CAPTCHA module on Custom Razor Page #10685


User avatar
0
vavrol1 created

Hello ABP Support Team,

I am using ABP Commercial (MVC UI + Basic Theme) and noticed that the platform includes CAPTCHA support. However, I could not find any official documentation, tutorial, or sample demonstrating how to use it on custom pages/forms outside of the built-in account/authentication pages.

I would like to ask:

Is the ABP CAPTCHA module intended to be reusable on custom MVC/Razor Pages? If yes, what is the recommended integration approach? Are there any examples or hidden/internal documentation available? Does it require using specific tag helpers, services, or middleware? Is the feature based on Google reCAPTCHA, or does ABP provide its own abstraction/component?

For example, I would like to protect a custom public form/page in my application using the same CAPTCHA mechanism provided by ABP.

I would appreciate any guidance or sample implementation.

Thank you.

Markdown supported.
Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)

1 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    Hi,

    ABP's CAPTCHA is Google reCAPTCHA (v2 and v3) wrapped via the Owl.reCAPTCHA NuGet package. The services are registered as public DI dependencies in the Volo.Abp.Account.Pro.Public.Web module, so you can reuse them on any custom Razor Page. No middleware, no extra module to depend on (your *.Web project already references the Account Pro Web module).

    SiteKey / SiteSecret / Version / Score all come from ABP settings (Abp.Account.Captcha.*), the same place the built-in Login/Register pages read from. So your custom page automatically uses the same keys you configured under Settings → Account → reCAPTCHA in the admin UI.

    Here is a minimal working example. I built and verified it in a fresh MVC + Basic Theme app on ABP 10.4.

    Pages/MyForm.cshtml.cs:

    using System.ComponentModel.DataAnnotations;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Options;
    using Owl.reCAPTCHA;
    using Volo.Abp.Account.Public.Web.Security.Recaptcha;
    using Volo.Abp.Account.Security.Recaptcha;
    using Volo.Abp.Account.Settings;
    using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
    using Volo.Abp.Settings;
    
    namespace MyApp.Web.Pages;
    
    public class MyFormModel : AbpPageModel
    {
        [BindProperty]
        public InputModel Input { get; set; }
    
        public int ReCaptchaVersion { get; set; }
    
        public IAbpRecaptchaValidatorFactory RecaptchaValidatorFactory =>
            LazyServiceProvider.LazyGetRequiredService<IAbpRecaptchaValidatorFactory>();
    
        public IOptionsSnapshot<reCAPTCHAOptions> ReCaptchaOptions =>
            LazyServiceProvider.LazyGetRequiredService<IOptionsSnapshot<reCAPTCHAOptions>>();
    
        public virtual async Task<IActionResult> OnGetAsync()
        {
            ReCaptchaVersion = await SettingProvider.GetAsync<int>(AccountSettingNames.Captcha.Version);
            await ReCaptchaOptions.SetAsync(ReCaptchaVersion == 3 ? reCAPTCHAConsts.V3 : reCAPTCHAConsts.V2);
            return Page();
        }
    
        public virtual async Task<IActionResult> OnPostAsync()
        {
            ReCaptchaVersion = await SettingProvider.GetAsync<int>(AccountSettingNames.Captcha.Version);
            await ReCaptchaOptions.SetAsync(ReCaptchaVersion == 3 ? reCAPTCHAConsts.V3 : reCAPTCHAConsts.V2);
    
            var validator = await RecaptchaValidatorFactory.CreateAsync();
            await validator.ValidateAsync(HttpContext.Request.Form[RecaptchaValidatorBase.RecaptchaResponseKey]);
    
            Alerts.Success("CAPTCHA verified. Message: " + Input.Message);
            return Page();
        }
    
        public class InputModel
        {
            [Required]
            public string Message { get; set; }
        }
    }
    

    Pages/MyForm.cshtml:

    @page
    @using Microsoft.AspNetCore.Mvc.TagHelpers
    @using Owl.reCAPTCHA.v2.TagHelpers
    @using Owl.reCAPTCHA.v3.TagHelpers
    @using Volo.Abp.Account.Public.Web.Security.Recaptcha
    @model MyApp.Web.Pages.MyFormModel
    
    @section scripts {
        @if (Model.ReCaptchaVersion == 3)
        {
            <recaptcha-script-v3 />
            <recaptcha-script-v3-js action="myform" execute="false" />
            <script>
                $("#myForm").submit(function (e) {
                    e.preventDefault();
                    var form = $(this);
                    grecaptcha.reExecute(function (token) {
                        form.find("input[type=hidden][data-captcha=true]").val(token);
                        form[0].submit();
                    });
                });
            </script>
        }
        else
        {
            <recaptcha-script-v2 />
            <script>
                function recaptchaCallback(token) {
                    $('#@RecaptchaValidatorBase.RecaptchaResponseKey').val(token);
                    $('#myForm button').removeAttr("disabled");
                }
            </script>
        }
    }
    
    <h2>My Custom Form</h2>
    
    <form id="myForm" method="post">
        <input type="hidden" data-captcha="true"
               name="@RecaptchaValidatorBase.RecaptchaResponseKey"
               id="@RecaptchaValidatorBase.RecaptchaResponseKey" />
    
        <div class="mb-3">
            <label asp-for="Input.Message" class="form-label"></label>
            <input asp-for="Input.Message" class="form-control" />
            <span asp-validation-for="Input.Message" class="text-danger"></span>
        </div>
    
        @if (Model.ReCaptchaVersion == 2)
        {
            <div class="mb-3">
                <recaptcha-div-v2 callback="recaptchaCallback" />
            </div>
            <button type="submit" class="btn btn-primary" disabled>Submit</button>
        }
        else
        {
            <button type="submit" class="btn btn-primary">Submit</button>
        }
    </form>
    

    A few notes:

    • The Pages/_ViewImports.cshtml generated by the ABP template already has @addTagHelper *, Owl.reCAPTCHA, so the recaptcha tag helpers work out of the box.
    • IAbpRecaptchaValidatorFactory.CreateAsync() returns a V2 or V3 validator based on the Abp.Account.Captcha.Version setting. You don't pick the version manually in code.
    • RecaptchaValidatorBase.RecaptchaResponseKey is the constant "g-recaptcha-response" — the standard hidden field name Google expects. Both the cshtml and the POST handler use this constant so they stay in sync.
    • await ReCaptchaOptions.SetAsync(...) is what injects SiteKey / SiteSecret from ABP settings into Owl.reCAPTCHA at request time. If you forget this, the tag helpers won't render with your configured keys.
    • ValidateAsync throws UserFriendlyException on failure, and ABP's exception filter renders it as an error alert automatically — no try/catch needed in the simple case.
    • For v3, the validator also enforces a score threshold (Abp.Account.Captcha.Score, default 0.5). If your custom page sees ScoreBelowThresholdException, lower this value under Settings → Account → reCAPTCHA → Score (e.g. 0.3), especially when testing on localhost where Google tends to return lower scores.

    If you want to read the built-in reference implementations, look at these files in the Volo.Abp.Account.Pro.Public.Web module: Pages/Account/Login.cshtml(.cs), Pages/Account/Register.cshtml(.cs), and Pages/Account/ForgotPassword.cshtml(.cs). The example above is essentially the same pattern as Register.cshtml.

    Thanks

    Markdown supported.
    Copy, paste, or drag & drop images and files (max 100 MB per file, 100 MB total per post)
Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.8.0-preview. Updated on September 21, 2026, 06:18
1
ABP Assistant
🔐 You need to be logged in to use the chatbot. Please log in first.