TimothyMeadows / reCAPTCHA.AspNetCore

Google reCAPTCHA v2/v3 for .NET Core 3.x

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

reCAPTCHA v3 - Bad score evaluation?

masherak opened this issue · comments

Sometimes, randomly I get score 0 on the server-side. Hidden input is correctly in the form, but if I look to the HttpRequest object I can see in the form-data google ReCaptcha field, but without any value. Do you know how I can fix it?

This seems like it might be an issue with the google servers, that score is populated strictly from there response to the recatpcha code supplied in the request at the time. If you are getting this a lot you might want to try turning the Recatpcha.net setting found here, or contacting google recatpcha team with details.

If you feel this in error you are free to reopen this, however, at present i am unsure what i could do.

This is caused by the javascript code validating the recaptcha on site load rather than on form submission:

Note: reCAPTCHA tokens expire after two minutes. If you're protecting an action with reCAPTCHA, make sure to call execute when the user takes the action rather than on page load.
https://developers.google.com/recaptcha/docs/v3#placement_on_your_website

I have rewritten my frontend code for recaptcha to use a jquery validator so that it pulls the recaptcha at the correct time in the submission process. This has an interesting effect of boosting the recaptcha scores overall because more useful recaptcha analytics are being sent to the Google server for evaluation.

I have created a custom tag helper on my project instead of using RecaptchaV3HiddenInput to solve this problem. You need to manually load reCAPTCHA script before using this.

namespace Ultima.IdentityProvider.TagHelpers
{
    using System.Text;
    using Microsoft.AspNetCore.Razor.TagHelpers;
    using Microsoft.Extensions.Options;
    using reCAPTCHA.AspNetCore;

    [HtmlTargetElement("button", Attributes = EnabledAttribute)]
    public sealed class EnabledCaptcha : TagHelper
    {
        public const string EnabledAttribute = "asp-enabled-captcha";

        private readonly RecaptchaSettings options;

        public EnabledCaptcha(IOptions<RecaptchaSettings> options)
        {
            this.options = options.Value;
        }

        [HtmlAttributeName(EnabledAttribute)]
        public bool Enabled { get; set; }

        public override void Process(TagHelperContext context, TagHelperOutput output)
        {
            if (!this.Enabled)
            {
                return;
            }

            var pre = new StringBuilder();

            // Hidden input.
            var token = context.UniqueId + "_token";

            pre.AppendFormat("<input id=\"{0}\" name=\"g-recaptcha-response\" type=\"hidden\" value=\"\"/>", token);

            // onclick script.
            var handler = context.UniqueId + "_onclick";

            pre.Append("<script>");
            pre.Append("function " + handler + "(b,e)");
            pre.Append("{");
            pre.Append("e.preventDefault();");
            pre.Append("b.disabled=true;");
            pre.Append("const form=b.form;");
            pre.Append("$(form).find(':input').prop('readonly', true);");
            pre.Append("grecaptcha.ready(function(){");
            pre.Append("grecaptcha.execute('" + this.options.SiteKey + "',{action:'submit'}).then(function(token){");
            pre.Append("document.getElementById('" + token + "').value=token;");
            pre.Append("form.submit();");
            pre.Append("});");
            pre.Append("});");
            pre.Append("}");
            pre.Append("</script>");

            // Set onclick handler.
            output.PreElement.AppendHtml(pre.ToString());
            output.Attributes.Add("onclick", handler + "(this, event)");
        }
    }
}

Here is the example of how to use it:

<button type="submit" asp-enabled-captcha="true">Submit</button>

I can submit a PR if we can find a good solution to load reCAPTCHA script only once.

I guess as i understand this, and why i re-opened it. It seems the primary issue is that with V3 you get ranked poorly the longer the user sits on the page? The problem is i think this is by design for google as part of the process? The original idea of the 2 minute timer when put in place was that it shouldn't taken that long ideally. If we move the entire process out of OnPageRender and into OnSubmit we are more or less bypassing part of the security it seems?

I acknowledge the link, and post with google themselves saying that this can be done, but the question is more, should it really be done?

@TimothyMeadows That's not quite how I understand it. From the documentation and video provided by Google, I have the impression that the recaptcha v3 script monitors our activity on the page. When we call grecaptcha.execute, it is requesting a score to be generated based on the analysis so far for that user. The score will actually be higher the longer you wait to make that API call because Google will have more information proving that the user is a real person.

I think that the reason the score is zero sometimes, is because Google's cache of the score only lasts two minutes. So if we try to verify after two minutes, then it's not on their server, and they return a 0 as a default score.