hcaptcha is a popular captcha service that is used to verify that a user is a human rather than a bot. Captcha, which stands for "Completely Automated Public Turing test to tell Computers and Humans Apart", is a test that is designed to be easy for humans to solve but difficult for machines. Captcha services are used to protect websites from automated spam and abuse. While there are many captcha services available, hcaptcha is becoming increasingly popular due to its superior security and usability compared to other captcha services. In our previous blog post, we saw how to use hcaptcha in your Asp.Net Core Razor Pages with page filters, in this blog post I will use hcaptcha in my Asp.Net Core controller actions with action filters. We want to create a contact page to receive contact messages from our audience. In this blog post, we assume that you have implemented the first seven steps of the previous blog post.

1-Create contact get and post methods

In this stage you should add get and post actions to your controller. You also need to create a contactUsModel for the post action.

public class HomeController : Controller
{
    .
    .
    [HttpGet]
    [ConfigureHCaptchaActionFilter]
    public IActionResult ContactUs()
    {
        return View();
    }
    [HttpPost]
    [ProtectByHCaptchaActionFilter]
    public IActionResult ContactUs(ContactUsModel contactUsModel)
    {
        //Here you can store the contact message.
        return View();
    }
    .
    .    
}
public class ContactUsModel
{
  [Required]
  [StringLength(50, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 1)]
  public string FullName { get; set; }

  [Required]
  [EmailAddress]
  [StringLength(50, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
  public string Email { get; set; }

  [Required]
  [StringLength(500, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 1)]
  public string Body { get; set; }

  [Url]     
  [StringLength(50, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
  public string WebAddress { get; set; }
}

2-Create action filters

To employ the provider and verifier in a loosely coupled manner for our hcaptcha, we have created two action filters, namely ConfigureHCaptchaActionFilter and ProtectByHCaptchaActionFilter. We just annotate the get method with the former action filter and the post method with the latter action filter. The former action filter receives the SiteKey and adds it to a ViewData. Later in our client-side code, we will use SiteKey for initializing the hcaptcha component. The latter action filter uses the verifier to verify the received token. If the verification fails, it returns a bad request response. Needless to say, we actively send the token to the server through form data.

[AttributeUsage(AttributeTargets.Method)]
public class ConfigureHCaptchaActionFilter : Attribute, IAsyncActionFilter
{
  public async Task OnActionExecutionAsync
    (ActionExecutingContext context, ActionExecutionDelegate next)
  {
    var hCaptchaConfigurationProvider = context.HttpContext
      .RequestServices.GetService<HCaptchaConfigurationProvider>();

    var controller = context.Controller as Controller;

    controller.ViewData["hCaptchaSitekey"] =
      hCaptchaConfigurationProvider.GetSiteKey();

    await next();
  }
}
[AttributeUsage(AttributeTargets.Method)]
public class ProtectByHCaptchaActionFilter : Attribute, IAsyncActionFilter
{
  public async Task OnActionExecutionAsync
    (ActionExecutingContext context, ActionExecutionDelegate next)
  {
    var hCaptchaVerifier = context.HttpContext
      .RequestServices.GetService<HCaptchaVerifier>();

    string hCaptchaToken =
      context.HttpContext.Request.Form["h-captcha-response"];

    var verificationResult =
      await hCaptchaVerifier.Verify(hCaptchaToken);
    if (!verificationResult)
    {
      context.Result =
        new BadRequestObjectResult("hCaptcha Token is invalid!");
      return;
    }

    await next();
  }
}

3-Add HCaptcha client-side files

We assume that you learned enough about "h-captcha.js" in our previous blog post. In addition to this file, you need to create another JavaScript file to collect data from inputs and add them as form data. I have created a file named "contact-us.js." After collecting data, submitContact method sends the data to the ContactUs post action along with hcaptcha and RequestVerificationToken tokens. If the form gets submitted successfully, an alert indicating the success will be shown. Otherwise, an alert indicating failure will be shown.

function submitContact() {
  if (!$("#contact-form").valid()) {
    return;
  }

  const url = $("#contact-form").attr("action");
  const requestVerificationToken = $('[name=__RequestVerificationToken]').val();
  const hCaptchaResponse = $('[name=h-captcha-response]').val();
  const fullNameValue = $('#FullName').val();
  const emailValue = $('#Email').val();
  const webAddressValue = $('#WebAddress').val();
  const bodyValue = $('#Body').val();

  const formData = new FormData();
  formData.append("__RequestVerificationToken", requestVerificationToken);
  formData.append("h-captcha-response", hCaptchaResponse);   
  formData.append("fullName", fullNameValue);
  formData.append("email", emailValue);
  formData.append("webAddress", webAddressValue);
  formData.append("body", bodyValue);

  $.ajax({
    type: 'POST',
    url: url,
    data: formData,
    processData: false,
    contentType: false,
    success: function (data) {
      alert("Your message was sent successfully.");
    },
    error: function (req, status, error) {
      alert("Your message was not sent! Please try again later.");
    }
  });
}

4- Create a contact view

To send a contact message to the server, I have designed a simple web page containing four inputs, namely full name, email, website, and body, and a submit button. Firstly, create a hcaptcha wrapper div with an id equal to hcaptcha-wrapper.” Then, assign the id submit-buttonto the submit button. We disable the button by default to prevent the user from submitting the form without the token. After that, assign the id contact-form to your form. Finally, create a script section at the end of the file and put the following codes in it. The following code adds the official hcaptcha JavaScript file along with our code helping us with sending the token to the server. We also pass the SiteKey's value to the hcaptcha initializer method via ViewData.

<div class="row">
  <div class="col-md-12">
    <div class="contact-wrapper">
      <h3>Get In Touch With Us</h3>
    </div> <!-- /.contact-wrapper -->
  </div> <!-- /.col-md-12 -->
  <div class="col-md-12">
    <form id="contact-form" asp-controller="Home" asp-action="ContactUs" method="post" enctype="multipart/form-data">
      <div class="row">
        <div class="text-danger" asp-validation-summary="ModelOnly"></div>
        <div class="col-md-4">
          <div class="form-group">
            <input type="text" asp-for="FullName" class="form-control" placeholder="Full Name *">
            <span style="font-size:12px;" class="text-danger" asp-validation-for="FullName"></span>
          </div>
        </div>
        <div class="col-md-4">
          <div class="form-group">
            <input type="email" asp-for="Email" class="form-control" placeholder="E-mail *">
            <span style="font-size:12px;" class="text-danger" asp-validation-for="Email"></span>
          </div>
        </div>
        <div class="col-md-4">
          <div class="form-group">
            <input type="text" asp-for="WebAddress" class="form-control" placeholder="Web-Address">
            <span style="font-size:12px;" class="text-danger" asp-validation-for="WebAddress"></span>
          </div>
        </div>
        <div class="col-md-12">
          <div class="form-group">
            <textarea asp-for="Body" class="form-control" placeholder="Body *" rows="8"></textarea>
            <span style="font-size:12px;" class="text-danger" asp-validation-for="Body"></span>
          </div>
        </div>
        <div id="hcaptcha-wrapper" class="col-md-4">
          <input id="submit-button" onclick="submitContact();" type="button" class="w-100 btn btn-lg btn-primary" value="Send Request" disabled>
        </div>
      </div>
    </form>
  </div>
</div>

@section Scripts
{
  <partial name="_ValidationScriptsPartial" />
  <script src="https://js.hcaptcha.com/1/api.js" async defer></script>
  <script src="~/js/contact-us.js"></script>
  <script src="~/js/h-captcha.js"></script>
  <script>
    initializeHCaptcha('@ViewData["hCaptchaSitekey"]');
  </script>
}