Search code examples
c#asp.net-coreasp.net-core-mvcrecaptcharecaptcha-enterprise

Google reCAPTCHA Enterprise with ASP.NET CORE 3.1


After some hours spent searching the web for implementation of Google reCAPTCHA Enterprise with ASP.NET CORE 3.1, I must, unfortunately, admit that I was not able to find anything I could use in my project. I've read the docs following the official site, but in the end, I'm still stucking for a clean implementation.

In ASP.NET Monsters there is an example, but targeting reCAPTCHA V3 and not reCAPTCHA enterprise.

There is also a nice post here Google ReCaptcha v3 server-side validation using ASP.NET Core 5.0, but again on reCAPTCHA V3.

Any help is appreciated.


Solution

  • So for me i needed to implement google recapthca with dotnet 5 using an angular front end. I am sure you can replace the angular front end with the native javascript instead, but this took me hours of investigating so hopefully it will help people.

    First i had to enable reCAPTCHA Enterprise, to do this i went to https://cloud.google.com/recaptcha-enterprise/ and then clicked on the "go to console" button. This took me to my Google Cloud Platform. From here i needed to create a key, fill in the options and save. This key will be referred to as your SITE_KEY.

    -- IF YOU ARE USING ANGULAR, READ THIS, ELSE SKIP THIS STEP AND IMPLEMENT IT YOURSELF

    On the client i used ng-recaptcha, you can find it here

    To implement this component, i added this import to my app.module.ts

    import { RECAPTCHA_V3_SITE_KEY } from 'ng-recaptcha';
    

    and this to the providers section

          {
              provide: RECAPTCHA_V3_SITE_KEY,
              useValue: SITE_KEY_GOES_HERE
          }
    

    On my component, when the submit button is pressed, i used the ReCaptchaV3Service from the library above. My code looks like this

    this.recaptchaV3Service.execute(YOUR_ACTION_NAME).subscribe((recaptchaResponse) => {
        // now call your api on the server and make sure you pass the recaptchaResponse string to your method
        });
    

    The text YOUR_ACTION_NAME is the name of the action you are doing. In my case i passed 'forgotPassword' as this parameter.

    -- END OF ANGULAR PART

    Now on the server, first i included this into my project

    <PackageReference Include="Google.Cloud.RecaptchaEnterprise.V1" Version="1.2.0" />
    

    Once included in my service, i found it easier to create a service in my code, which is then injected. I also created a basic options class, which is injected into my service, it can be injected into other places if needed.

    RecaptchaOptions.cs

        public class RecaptchaOptions
        {
            public string Type { get; set; }
    
            public string ProjectId { get; set; }
    
            public string PrivateKeyId { get; set; }
    
            public string PrivateKey { get; set; }
    
            public string ClientEmail { get; set; }
    
            public string ClientId { get; set; }
    
            public string SiteKey { get { return YOUR_SITE_KEY; } }
    
            /// <summary>
            /// 0.1 is worst (probably a bot), 0.9 is best (probably human)
            /// </summary>
            public float ExceptedScore { get { return (float)0.7; } }
        }
    

    Some of these values are not used, but i have added them for the future, encase i do use them.

    Then i have created my service, which looks like so (i have created an interface for injecting and testing)

    IRecaptchaService.cs

    public interface IRecaptchaService
    {
        Task<bool> VerifyAsync(string recaptchaResponse, string expectedAction);
    }
    

    RecaptchaService.cs

    public class RecaptchaService : IRecaptchaService
    {
        #region IRecaptchaService
    
        /// <summary>
        /// Check our recaptcha
        /// </summary>
        /// <param name="recaptchaResponse">The response from the client</param>
        /// <param name="expectedAction">The action that we are expecting</param>
        /// <returns></returns>
        public async Task<bool> VerifyAsync(string recaptchaResponse, string expectedAction)
        {
            // initialize request argument(s)
            var createAssessmentRequest = new CreateAssessmentRequest
            {
                ParentAsProjectName = ProjectName.FromProject(_recaptchaOptions.ProjectId),
                Assessment = new Assessment()
                {
                    Event = new Event()
                    {
                        SiteKey = _recaptchaOptions.SiteKey,
                        Token = recaptchaResponse
                    }
                },
            };
    
            // client
            var cancellationToken = new CancellationToken();
            var client = RecaptchaEnterpriseServiceClient.Create();
    
            // Make the request
            try
            {
                var response = await client.CreateAssessmentAsync(createAssessmentRequest, cancellationToken);
    
                return response.TokenProperties.Valid && response.TokenProperties.Action.Equals(expectedAction) && response.RiskAnalysis?.Score >= _recaptchaOptions.ExceptedScore;
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
    
                return false;
            }
    
        }
    
        #endregion
    
        private RecaptchaOptions _recaptchaOptions;
    
        public RecaptchaService(RecaptchaOptions recaptchaOptions)
        {
            _recaptchaOptions = recaptchaOptions;
        }
    }
    

    Now my api endpoint, i inject this service and call it. Here is an example API method that calls the recaptchaService.

    public async Task<IActionResult> ForgotPasswordAsync([FromBody] ForgotPasswordModel model)
    {
        // check our recaptchaResponse
        var verified = await _recaptchaService.VerifyAsync(model.RecaptchaResponse, "forgotPassword");
    
        if (!verified)
            throw new ApplicationException("Recaptcha failed, please try again");
    
        // successful, carry on
    }
    

    Hope this helps everyone, if there are any questions, please ask and i will edit this and update it with anything i have missed.