Skip to content

♻️ A friction-less C# HTTP verification client for Google's reCAPTCHA API.

License

Notifications You must be signed in to change notification settings

BitArmory/ReCaptcha

Repository files navigation

Build status Nuget Users

BitArmory.ReCaptcha for .NET and C#

Project Description

♻️ A minimal, no-drama, friction-less C# HTTP verification client for Google's reCAPTCHA API.

📢 HEY! Be sure to checkout these other captcha integrations:

  • BitArmory.Turnstile - Cloudflare's captcha verification service; a good alternative to Google's reCAPTCHA!

The problem with current ReCaptcha libraries in .NET is that all of them take a hard dependency on the underlying web framework like ASP.NET WebForms, ASP.NET MVC 5, ASP.NET Core, or ASP.NET Razor Pages.

Furthermore, current reCAPTCHA libraries for .NET are hard coded against the HttpContext.Request to retrieve the remote IP address of the visitor. Unfortunately, this method doesn't work if your website is behind a service like CloudFlare where the CF-Connecting-IP header value is the real IP address of the visitor on your site.

BitArmory.ReCaptcha is a minimal library that works across all .NET web frameworks without taking a hard dependency on any web framework. If you want to leverage platform specific features, like MVC Action Filters, you'll need to implement your own ActionFilter that leverages the functionality in this library.

Supported Platforms

  • .NET Standard 1.3 or later
  • .NET Framework 4.5 or later

Supported reCAPTCHA Versions

Crypto Tip Jar

  • 🐕 Dogecoin: DGVC2drEMt41sEzEHSsiE3VTrgsQxGn5qe

Download & Install

NuGet Package BitArmory.ReCaptcha

Install-Package BitArmory.ReCaptcha

Full Examples

Various full and complete examples can be found here:

reCAPTCHA v3 (Invisible)

General Usage

Getting Started

You'll need to create reCAPTCHA account. You can sign up here! After you sign up and setup your domain, you'll have two important pieces of information:

  1. Your site key
  2. Your secret key

This library supports both:

reCAPTCHA v3 (Invisible)

Client-side Setup

Be sure to checkout this video that describes how reCAPTCHA v3 works before implementing.

Then, on every page of your website, add the following JavaScript:

<html>
  <head>
    <script src='https://www.google.com/recaptcha/api.js?render=GOOGLE_SITE_KEY'></script>
  </head>
  <body>
    ...
    <script>
        grecaptcha.ready(function() {
          grecaptcha.execute('GOOGLE_SITE_KEY', {action: 'TAG'});
        });
    </script>
  </body>
</html>

Every page should call grecaptcha.execute with some unique action TAG. Read more about actions in the official docs here.

When it is time to validate an HTTP POST you'll need transfer the captcha token in the browser to a hidden HTML form field as shown below:

<html>
  <body>
    <form action='/do-post' method='POST'>
      <input id="captcha" type="hidden" name="captcha" value="" />
    </form>
    <script>
      function ExecuteReCaptcha_OnSome_ButtonAction(){
        grecaptcha.ready(function() {
          grecaptcha.execute('GOOGLE_SITE_KEY', {action: 'SomeAction'})
            .then(function(token) {
               // Set `token` in a hidden form input.
               $("#captcha").val(token);
               
               //And finally submit the form by firing
               //off the HTTP POST over the wire at this
               //exact moment in time here.
            });
        });
      }
    </script>
  </body>
</html>

You'll need to execute ExecuteReCaptcha_OnSome_ButtonAction() function the moment the user decides to submit your form. Otherwise, if you run grecaptcha.* code during page load, the token being copied to the hidden field can expire after a few minutes. This means, if the user takes a long time filling out a form, the token copied at page load can expire and your server will validate an expired token by the time the form is submitted resulting in a failed captcha verification.

Therefore, you should execute the ExecuteReCaptcha_OnSome_ButtonAction() function on some onclick= event to get a fresh token before the form is submitted.

Also, keep in mind, grecaptcha.execute() returns a JavaScript Promise. You won't have a valid token in your <form> until the line $("#captcha").val(token); above executes. So you'll need to postpone the form submission until $("#captcha").val(token); is actually executed. Then, and only then, you can continue submitting the HTML form to have it validated on your server with a valid token.

Verifying the POST Server-side

When the POST is received on the server:

  1. Get the client's IP address. If you're using CloudFlare, be sure to use the CF-Connecting-IP header value.
  2. Extract the #captcha value (client token) in the hidden HTML form field.
  3. Use the ReCaptchaService to verify the client's reCAPTCHA is valid.
//1. Get the client IP address in your chosen web framework
string clientIp = GetClientIpAddress();
string token = null;
string secret = "your_secret_key";

//2. Extract the `#captcha` field from the hidden HTML form in your chosen web framework
if( this.Request.Form.TryGetValue("captcha", out var formField) )
{
   token = formField;
}

//3. Validate the reCAPTCHA with Google
var captchaApi = new ReCaptchaService();
var result = await captchaApi.Verify3Async(token, clientIp, secret);

if( !result.IsSuccess || result.Action != "SOME_ACTION" || result.Score < 0.5 )
{
   // The POST is not valid
   return new BadRequestResult();
}
else{
   //continue processing, everything is okay!
}
GetClientIpAddress() in ASP.NET Core

Note: If your site is behind CloudFlare, be sure you're suing the CF-Connecting-IP header value instead.

public string GetClientIpAddress(){
   return this.HttpContext.Connection.RemoteIpAddress.ToString();
}

GetClientIpAddress() in ASP.NET WebForms

Note: If your site is behind CloudFlare, be sure you're suing the CF-Connecting-IP header value instead.

public string GetClientIpAddress(){
   return this.Request.UserHostAddress;
}

You'll want to make sure the action name you choose for the request is legitimate. The result.Score is the probably of a human. So, you'll want to make sure you have a result.Score > 0.5; anything less is probably a bot.

reCAPTCHA v2 (I'm not a robot)

Client-side Setup

Add the following <div class="g-recaptcha"> and <script> tags to your HTML form:

<html>
  <body>
    <form method="POST">
        ...
        <div class="g-recaptcha" data-sitekey="your_site_key"></div>
        <input type="submit" value="Submit">
    </form>

    <script src="https://www.google.com/recaptcha/api.js" async defer></script>
  </body>
</html>

Verifying the POST Server-side

When the POST is received on the server:

  1. Get the client's IP address. If you're using CloudFlare, be sure to use the CF-Connecting-IP header value.
  2. Extract the g-recaptcha-response (Client Response) HTML form field.
  3. Use the ReCaptchaService to verify the client's reCAPTCHA is valid.

The following example shows how to verify the captcha during an HTTP POST back in ASP.NET Core: Razor Pages.

//1. Get the client IP address in your chosen web framework
string clientIp = GetClientIpAddress();
string captchaResponse = null;
string secret = "your_secret_key";

//2. Extract the `g-recaptcha-response` field from the HTML form in your chosen web framework
if( this.Request.Form.TryGetValue(Constants.ClientResponseKey, out var formField) )
{
   capthcaResponse = formField;
}

//3. Validate the reCAPTCHA with Google
var captchaApi = new ReCaptchaService();
var isValid = await captchaApi.Verify2Async(capthcaResponse, clientIp, secret);
if( !isValid )
{
   this.ModelState.AddModelError("captcha", "The reCAPTCHA is not valid.");
   return new BadRequestResult();
}
else{
   //continue processing, everything is okay!
}
GetClientIpAddress() in ASP.NET Core

Note: If your site is behind CloudFlare, be sure you're suing the CF-Connecting-IP header value instead.

public string GetClientIpAddress(){
   return this.HttpContext.Connection.RemoteIpAddress.ToString();
}

GetClientIpAddress() in ASP.NET WebForms

Note: If your site is behind CloudFlare, be sure you're suing the CF-Connecting-IP header value instead.

public string GetClientIpAddress(){
   return this.Request.UserHostAddress;
}

That's it! Happy verifying! 🎉

Building

  • Download the source code.
  • Run build.cmd.

Upon successful build, the results will be in the \__compile directory. If you want to build NuGet packages, run build.cmd pack and the NuGet packages will be in __package.