Add CAPTCHA to the Registration form widget
Overview
To improve security, you can add a CAPTCHA to the Registration form widget. You can use the provided Google reCAPTCHA templates or plug in your own CAPTCHA.
PREREQUISITES: Before using Google reCAPTCHA, you must:
- Register your site get an API key pair at Google reCAPTCHA.
You can generate an API key for either reCAPTCHA v2 or reCAPTCHA v3 and then use it with the corresponding widget from this tutorial.- You must update your privacy and GDPR statements according to Google reCAPTCHA policies
Configure CAPTCHA settings
Perform the following:
- In Sitefinity backend, navigate to Administration » Settings » Advanced.
- In the treeview, expand WebSecurity » Captcha and click Parameters.
- Configure the following parameters:
- Key
VerificationUrland Valuehttps://www.google.com/recaptcha/api/siteverify - Key
SecretKeyand Value<The secret from the API key pair that you have created> - Key
EnableCaptchaValidationand Valuetrue
This will enable the CAPTCHA validation in the Registration form widget.
- Key
- Save your changes.
Create the CAPTCHA Registration widget template
Create a custom template for the Registration form widget in the following way:
- Open your project in Visual Studio.
- In folder
Views/Shared/Components, create a new folder and name itSitefinityRegistration - In the context menu of folder
SitefinityRegistration, click Add» New Item… - Depending on which version of CAPTCHA you want to use, perform one of the following:
-
Name the file
RegistrationWithCaptchaDemoV2.cshtml, and click Add -
In the template paste the following code:
HTML+Razor@model Progress.Sitefinity.AspNetCore.Widgets.Models.Registration.RegistrationViewModel @using Progress.Sitefinity.AspNetCore.Widgets.Models.Registration @using Progress.Sitefinity.AspNetCore.Mvc.Rendering @using Progress.Sitefinity.AspNetCore.ViewComponents; @using Progress.Sitefinity.AspNetCore.Web @inject IRenderContext renderContext; @{ var lbls = Model.Labels; } <script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" section-name="Bottom" async defer></script> <script type="text/javascript"> (function () { var responseKey = "sf_captcha_response"; var siteKey = '<your_site_key>'; window.onloadCallback = renderCaptcha; function responseCallback(response) { var form = document.querySelector('form'); var responseInput = form.querySelector('input[name="' + responseKey + '"]'); if (!responseInput) { responseInput = document.createElement('input'); responseInput.type = 'hidden'; responseInput.name = responseKey; form.appendChild(responseInput); } responseInput.value = response; } function renderCaptcha() { grecaptcha.render('captchav2', { 'sitekey': siteKey, 'callback': responseCallback }); } })(); </script> @if (Model.IsAccountActivationRequest) { <div class="@(string.IsNullOrEmpty(Model.CssClass) ? null : Model.CssClass)" @Html.BuildAttributes(Model.Attributes)> <h2 class="mb-3"> @lbls.ActivationMessage </h2> </div> } else { <div data-sf-role="sf-registration-container" data-sf-visibility-hidden="@Model.VisibilityClasses[Progress.Sitefinity.AspNetCore.Configuration.VisibilityStyle.Hidden]" data-sf-invalid="@Model.InvalidClass" class="@(string.IsNullOrEmpty(Model.CssClass) ? null : Model.CssClass)" @Html.BuildAttributes(Model.Attributes)> @if (Model.ShowSuccessMessage(Context)) { <h3>@lbls.SuccessHeader</h3> <p>@lbls.SuccessLabel</p> } else { var firstNameInputId = Html.GetUniqueId("sf-first-name-"); var lastNameInputId = Html.GetUniqueId("sf-last-name-"); var emailInputId = Html.GetUniqueId("sf-email-"); var passwordInputId = Html.GetUniqueId("sf-new-password-"); var repeatPasswordInputId = Html.GetUniqueId("sf-repeat-password-"); var questionInputId = Html.GetUniqueId("sf-secret-question-"); var answerInputId = Html.GetUniqueId("sf-secret-answer-"); <environment include="Development"> <script src="Scripts/LoginWidgets/registration.js" section-name="Bottom" assembly-ref="Progress.Sitefinity.AspNetCore.Widgets"></script> </environment> <environment exclude="Development"> <script src="Scripts/LoginWidgets/registration.min.js" section-name="Bottom" assembly-ref="Progress.Sitefinity.AspNetCore.Widgets"></script> </environment> <div data-sf-role="form-container"> <h2 class="mb-3">@lbls.Header</h2> <div data-sf-role="error-message-container" class="alert alert-danger d-none my-3" role="alert" aria-live="assertive"></div> <form method="post" action="@Model.RegistrationHandlerPath" role="form" novalidate> <div class="mb-3"> <label for="@firstNameInputId" class="form-label">@lbls.FirstNameLabel</label> <input id="@firstNameInputId" type="text" class="form-control" name="FirstName" data-sf-role="required"> </div> <div class="mb-3"> <label for="@lastNameInputId" class="form-label">@lbls.LastNameLabel</label> <input id="@lastNameInputId" type="text" class="form-control" name="LastName" data-sf-role="required"> </div> <div class="mb-3"> <label for="@emailInputId" class="form-label">@lbls.EmailLabel</label> <input id="@emailInputId" type="email" class="form-control" name="Email" data-sf-role="required"> </div> <div class="mb-3"> <label for="@passwordInputId" class="form-label">@lbls.PasswordLabel</label> <input id="@passwordInputId" type="password" class="form-control" name="Password" data-sf-role="required"> </div> <div class="mb-3"> <label for="@repeatPasswordInputId" class="form-label">@lbls.RepeatPasswordLabel</label> <input id="@repeatPasswordInputId" type="password" class="form-control" name="RepeatPassword" data-sf-role="required"> </div> <div class="mb-3"> <div> <div id="captchav2" class="@("mb-3 " + (renderContext.IsEdit ? "pe-none" : ""))"></div> </div> </div> @if (Model.RequiresQuestionAndAnswer) { <div class="mb-3"> <label for="@questionInputId" class="form-label">@lbls.SecretQuestionLabel</label> <input id="@questionInputId" type="text" class="form-control" name="Question" data-sf-role="required"> </div> <div class="mb-3"> <label for="@answerInputId" class="form-label">@lbls.SecretAnswerLabel</label> <input id="@answerInputId" type="text" class="form-control" name="Answer" data-sf-role="required"> </div> } <input class="btn btn-primary w-100" type="submit" value="@lbls.RegisterButtonLabel" /> <input type="hidden" name="ActivationPageUrl" value="@Model.ActivationPageUrl" /> <input type="hidden" value="" name="sf_antiforgery" /> </form> @if (!string.IsNullOrEmpty(Model.LoginPageUrl)) { <div class="mt-3">@lbls.LoginLabel</div> <a href="@Model.LoginPageUrl" class="text-decoration-none">@lbls.LoginLink</a> } @if (Model.ExternalProviders != null && Model.ExternalProviders.Any()) { <h3 class="mt-3">@lbls.ExternalProvidersHeader</h3> @foreach (var provider in Model.ExternalProviders) { <a data-sf-test="extPrv" class="btn border fs-5 w-100 mt-2 @Model.GetExternalLoginButtonCssClass(provider.Name)" href="@Model.GetExternalLoginPath(Context, provider.Name)">@provider.Title</a> } } <input type="hidden" name="RedirectUrl" value="@Model.RedirectUrl" /> <input type="hidden" name="PostRegistrationAction" value="@Model.PostRegistrationAction" /> <input type="hidden" name="ActivationMethod" value="@Model.ActivationMethod" /> <input type="hidden" name="ValidationRequiredMessage" value="@lbls.ValidationRequiredMessage" /> <input type="hidden" name="ValidationMismatchMessage" value="@lbls.ValidationMismatchMessage" /> <input type="hidden" name="ValidationInvalidEmailMessage" value="@lbls.ValidationInvalidEmailMessage" /> </div> <div data-sf-role="success-registration-message-container" class="d-none"> <h3>@lbls.SuccessHeader</h3> <p>@lbls.SuccessLabel</p> </div> <div data-sf-role="confirm-registration-message-container" class="d-none"> <h3>@lbls.ActivationLinkHeader</h3> <p data-sf-role="activation-link-message-container"></p> <a href="javascript:void(0)" data-sf-role="sendAgainLink" class="btn btn-primary"> @lbls.SendAgainLink </a> <input type="hidden" name="ResendConfirmationEmailUrl" value="@Model.ResendConfirmationEmailHandlerPath" /> <input type="hidden" name="ActivationLinkLabel" value="@lbls.ActivationLinkLabel" /> <input type="hidden" name="SendAgainLink" value="@lbls.SendAgainLink" /> <input type="hidden" name="SendAgainLabel" value="@lbls.SendAgainLabel" /> </div> } </div> } -
In the template, change the
<your_site_key>variable to have as value your site. -
Save your changes and build your solution.
-
Name the file
RegistrationWithCaptchaDemoV3.cshtml, and click Add -
In the template paste the following code:
HTML+Razor@model Progress.Sitefinity.AspNetCore.Widgets.Models.Registration.RegistrationViewModel @using Progress.Sitefinity.AspNetCore.Widgets.Models.Registration @using Progress.Sitefinity.AspNetCore.Mvc.Rendering @using Progress.Sitefinity.AspNetCore.ViewComponents; @using Progress.Sitefinity.AspNetCore.Web @inject IRenderContext renderContext; @{ var lbls = Model.Labels; } <script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=<your_site_key>" section-name="Bottom"></script> <script type="text/javascript"> (function () { var responseKey = "sf_captcha_response"; var siteKey = '<your site key>'; window.onloadCallback = renderCaptcha; function renderCaptcha() { var form = document.querySelector('form'); var submitBtn = form.querySelector('input[type="submit"]'); var isSubmit = false; submitBtn.addEventListener('click', function (event) { if (!isSubmit) { event.preventDefault(); grecaptcha.ready(function () { grecaptcha.execute(siteKey, { action: 'submit' }).then(function (response) { var responseInput = form.querySelector('input[name="' + responseKey + '"]'); if (!responseInput) { responseInput = document.createElement('input'); responseInput.type = 'hidden'; responseInput.name = responseKey; form.appendChild(responseInput); } responseInput.value = response; isSubmit = true; submitBtn.click(); }); }); } else { isSubmit = false; } }); } })(); </script> @if (Model.IsAccountActivationRequest) { <div class="@(string.IsNullOrEmpty(Model.CssClass) ? null : Model.CssClass)" @Html.BuildAttributes(Model.Attributes)> <h2 class="mb-3"> @lbls.ActivationMessage </h2> </div> } else { <div data-sf-role="sf-registration-container" data-sf-visibility-hidden="@Model.VisibilityClasses[Progress.Sitefinity.AspNetCore.Configuration.VisibilityStyle.Hidden]" data-sf-invalid="@Model.InvalidClass" class="@(string.IsNullOrEmpty(Model.CssClass) ? null : Model.CssClass)" @Html.BuildAttributes(Model.Attributes)> @if (Model.ShowSuccessMessage(Context)) { <h3>@lbls.SuccessHeader</h3> <p>@lbls.SuccessLabel</p> } else { var firstNameInputId = Html.GetUniqueId("sf-first-name-"); var lastNameInputId = Html.GetUniqueId("sf-last-name-"); var emailInputId = Html.GetUniqueId("sf-email-"); var passwordInputId = Html.GetUniqueId("sf-new-password-"); var repeatPasswordInputId = Html.GetUniqueId("sf-repeat-password-"); var questionInputId = Html.GetUniqueId("sf-secret-question-"); var answerInputId = Html.GetUniqueId("sf-secret-answer-"); <environment include="Development"> <script src="Scripts/LoginWidgets/registration.js" section-name="Bottom" assembly-ref="Progress.Sitefinity.AspNetCore.Widgets"></script> </environment> <environment exclude="Development"> <script src="Scripts/LoginWidgets/registration.min.js" section-name="Bottom" assembly-ref="Progress.Sitefinity.AspNetCore.Widgets"></script> </environment> <div data-sf-role="form-container"> <h2 class="mb-3">@lbls.Header</h2> <div data-sf-role="error-message-container" class="alert alert-danger d-none my-3" role="alert" aria-live="assertive"></div> <form method="post" action="@Model.RegistrationHandlerPath" role="form" novalidate> <div class="mb-3"> <label for="@firstNameInputId" class="form-label">@lbls.FirstNameLabel</label> <input id="@firstNameInputId" type="text" class="form-control" name="FirstName" data-sf-role="required"> </div> <div class="mb-3"> <label for="@lastNameInputId" class="form-label">@lbls.LastNameLabel</label> <input id="@lastNameInputId" type="text" class="form-control" name="LastName" data-sf-role="required"> </div> <div class="mb-3"> <label for="@emailInputId" class="form-label">@lbls.EmailLabel</label> <input id="@emailInputId" type="email" class="form-control" name="Email" data-sf-role="required"> </div> <div class="mb-3"> <label for="@passwordInputId" class="form-label">@lbls.PasswordLabel</label> <input id="@passwordInputId" type="password" class="form-control" name="Password" data-sf-role="required"> </div> <div class="mb-3"> <label for="@repeatPasswordInputId" class="form-label">@lbls.RepeatPasswordLabel</label> <input id="@repeatPasswordInputId" type="password" class="form-control" name="RepeatPassword" data-sf-role="required"> </div> @if (Model.RequiresQuestionAndAnswer) { <div class="mb-3"> <label for="@questionInputId" class="form-label">@lbls.SecretQuestionLabel</label> <input id="@questionInputId" type="text" class="form-control" name="Question" data-sf-role="required"> </div> <div class="mb-3"> <label for="@answerInputId" class="form-label">@lbls.SecretAnswerLabel</label> <input id="@answerInputId" type="text" class="form-control" name="Answer" data-sf-role="required"> </div> } <input class="btn btn-primary w-100" type="submit" value="@lbls.RegisterButtonLabel" /> <input type="hidden" name="ActivationPageUrl" value="@Model.ActivationPageUrl" /> <input type="hidden" value="" name="sf_antiforgery" /> </form> @if (!string.IsNullOrEmpty(Model.LoginPageUrl)) { <div class="mt-3">@lbls.LoginLabel</div> <a href="@Model.LoginPageUrl" class="text-decoration-none">@lbls.LoginLink</a> } @if (Model.ExternalProviders != null && Model.ExternalProviders.Any()) { <h3 class="mt-3">@lbls.ExternalProvidersHeader</h3> @foreach (var provider in Model.ExternalProviders) { <a data-sf-test="extPrv" class="btn border fs-5 w-100 mt-2 @Model.GetExternalLoginButtonCssClass(provider.Name)" href="@Model.GetExternalLoginPath(Context, provider.Name)">@provider.Title</a> } } <input type="hidden" name="RedirectUrl" value="@Model.RedirectUrl" /> <input type="hidden" name="PostRegistrationAction" value="@Model.PostRegistrationAction" /> <input type="hidden" name="ActivationMethod" value="@Model.ActivationMethod" /> <input type="hidden" name="ValidationRequiredMessage" value="@lbls.ValidationRequiredMessage" /> <input type="hidden" name="ValidationMismatchMessage" value="@lbls.ValidationMismatchMessage" /> <input type="hidden" name="ValidationInvalidEmailMessage" value="@lbls.ValidationInvalidEmailMessage" /> </div> <div data-sf-role="success-registration-message-container" class="d-none"> <h3>@lbls.SuccessHeader</h3> <p>@lbls.SuccessLabel</p> </div> <div data-sf-role="confirm-registration-message-container" class="d-none"> <h3>@lbls.ActivationLinkHeader</h3> <p data-sf-role="activation-link-message-container"></p> <a href="javascript:void(0)" data-sf-role="sendAgainLink" class="btn btn-primary"> @lbls.SendAgainLink </a> <input type="hidden" name="ResendConfirmationEmailUrl" value="@Model.ResendConfirmationEmailHandlerPath" /> <input type="hidden" name="ActivationLinkLabel" value="@lbls.ActivationLinkLabel" /> <input type="hidden" name="SendAgainLink" value="@lbls.SendAgainLink" /> <input type="hidden" name="SendAgainLabel" value="@lbls.SendAgainLabel" /> </div> } </div> } -
In the template, change the
<your_site_key>variable to have as value your site. -
Save your changes and build your solution.
Apply the CAPTCHA template
Perform the following:
- Open the page where the Registration form widget is located for editing.
- In the page editor, hover over the Registration form widget.
- Click the toggle menu in the widget label.
- Click
(Edit). - In the Registration template dropdown box, select the new template.
- Save your changes.