12

For Google reCAPTCHA V2 it was clear what to do when the token gets expired because of idle: the customer has a change to click on the reCaptcha checkbox again. For Google reCAPTCHA V3 it is different as it is unclear when the token gets expired because of idle.

For reCAPTCHA V3 it is suggested by Google:

https://developers.google.com/recaptcha/docs/v3

  1. Load the JavaScript api with your sitekey

  2. Call grecaptcha.execute on an action or when the page loads // we choose when the page loads, OK?

  3. Send the token to your backend with the request to verify // upon button click

OK. If the button was clicked several minutes later than the page was loaded, the V3 token we send to backend is already expired. What's the proper way to deal in this situation? Should we silently auto-update the token by sending calls to Google every minute? What's the best approach for this case? I didn't find any suggestions from Google.

Haradzieniec
  • 9,086
  • 31
  • 117
  • 212
  • Then I would execute it on button click – Jonas Wilms Jan 30 '19 at 10:02
  • Did you figure this out? I'm having the same issue with the use case 3. I'm receiving the token at page load but it's expired by the time the form is send to the BE. – João Belo May 07 '19 at 12:50
  • 1
    @JoãoBelo Yes. The token must be generated later than on page load. It should be generated right before we send it to backend.With another words, we click on the button (from my example), then get the generated token, then send the token to my backend. – Haradzieniec May 17 '19 at 15:15

4 Answers4

16

Since reCAPTCHA tokens expire after two minutes, this is how I have put it to work:

Step 1: Load the captcha token on page load (as usual)

Step 2: Use a SetInterval function to reload the token every 90 seconds, so that the reCAPTCHA token is refreshed before it expires after 2 minutes.

// Onload
grecaptcha.ready(function () {
  grecaptcha.execute('YOUR_KEY_HERE', { action: 'request_call_back' }).then(function (e) {
    $('#YOUR_FIELD_NAME_ID').val(e);
  });
});

// Every 90 Seconds
setInterval(function () {
  grecaptcha.ready(function () {
    grecaptcha.execute('YOUR_KEY_HERE', { action: 'request_call_back' }).then(function (e) {
      $('#YOUR_FIELD_NAME_ID').val(e);
    });
  });
}, 90 * 1000);
Mario Petrovic
  • 7,500
  • 14
  • 42
  • 62
Vinay Modi
  • 347
  • 3
  • 7
11

The token must be generated later than on page load. It should be generated right before we send it to backend. With another words, we click on the button (from my example), then get the generated token, then send the token to backend.

This solution makes sense and fixes my issue.

Haradzieniec
  • 9,086
  • 31
  • 117
  • 212
  • 8
    The solution isn't perfect. Because, when we request the token just before the send. The form needs waiting few seconds to receive token and send with the data. – Rifton007 Feb 12 '20 at 20:38
2

I work in asp.net forms, but this solution can be applicable in any language. The problem of the expired token is annoying.

The token has a validity time of 2 minutes in v3, but the practice of leaving a timer refreshing the token every 2 minutes is not recommended by google. They recommend the token be refreshed only when required.

I opted for a javascript solution, forcing the client to click on a button that refreshes the token.

It should be noted that if "recaptcha.ready" is executed when refreshing the recaptcha, an error is thrown, so I had to separate the "ready" from the "execute" and with this the recaptcha is refreshed without errors.

<script type="text/javascript" >
    grecaptcha.ready(function () {
      captcha_execute();
    });

    function captcha_execute() {
      grecaptcha.execute('<%=System.Configuration.ConfigurationManager.AppSettings("recaptcha-public-key").ToString %>', { action: 'ingreso_usuario_ext' }).then(function (token) {
        document.getElementById("g-recaptcha-response").value = token;
      });
    }

    function los_dos(token_viejo) {
      captcha_execute()
      clase_boton(token_viejo);
    }

    async function clase_boton(token_viejo) {
      btn_act = document.getElementById("Btn_Refrescar");
      btn = document.getElementById("Btn_Ingresar");
      btn.setAttribute("class", "button_gris");
      btn_act.style.display = "none";
      btn.style.display = "initial";
      btn.disabled = true;

      //token_viejo = document.getElementById("g-recaptcha-response").value;
      strToken = token_viejo;
      varCant = 0;

      while (strToken == token_viejo && varCant < 30) {
        strToken = document.getElementById("g-recaptcha-response").value;
        await sleep(100);
        varCant++;
      }

      btn.setAttribute("class", "button_azul");
      btn.disabled = false;

      setTimeout(refrescar_token, 120000);
    }

    function sleep(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    function refrescar_token() {
      btn_ing = document.getElementById("Btn_Ingresar");
      btn_act = document.getElementById("Btn_Refrescar");
      btn_act.style.display = "initial";
      btn_ing.style.display = "none";
    }
  </script>

In the body

<body style="background-color: #dededc;" onload="clase_boton('');" >

Buttons

 <asp:Button ID="Btn_Ingresar" runat="server" Text="Ingresar" CssClass="button_gris" Enabled="false" />
 <input type="button" id="Btn_Refrescar" name="Btn_Refrescar" class="button_verde" value="Refrescar Token" title="Refrescar Token" onclick="los_dos(document.getElementById('g-recaptcha-response').value);" style="display: none;" />
          

With javascript, I wait for the token to be populated and when it is populated, I enable the login button. If the process takes too long (due to some error), I still enable it. This is a matter of choice.

After 2 minutes ("setTimeout"), the login button becomes invisible and I show the button to refresh the token.

I hope this helps/guides you solve your problem.

0

I had similar issue also. We just need to execute recaptcha before submitting form. For that, some tricky needed. Please refer my code.

let recaptcha_token=null;
$('form').submit(function (e){
        var that=this;
        if(!recaptcha_token){
            e.preventDefault();
            letr recaptcha_site_key='<%=recaptcha_site_key%>'
            grecaptcha.execute(recaptcha_site_key, {action:'verify'})
                .then(function(token) {
                    console.log(token);
                    $('.recaptcha_token').val(token);
                    recaptcha_token=token;
                    that.submit();
                });
        }else
            return true;
    });

So, when page loaded, recaptcha_token is null. When we submit form, because recaptcha_token is null, form submit event will be prevented. And after executing recaptcha, we get recaptcha_token and submit form again, then it will be submitted.

BaiMaoli
  • 168
  • 2
  • 15