Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

reCAPTCHA V3: how to deal with expired token after idle?

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.

like image 531
Haradzieniec Avatar asked Jan 30 '19 09:01

Haradzieniec


People also ask

What happens if you fail reCAPTCHA v3?

If the reCaptcha failed, then it, mostly, a bot. So no actual action is required. So it could be an ignore action - no response action at all.

How do I fix failed to validate a Google reCAPTCHA token?

There are a few steps you can take to improve your experience: Make sure your browser is fully updated (see minimum browser requirements) Check that JavaScript is enabled in your browser. Try disabling plugins that might conflict with reCAPTCHA.

How long do reCAPTCHA tokens last?

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. You can execute reCAPTCHA on as many actions as you want on the same page.

How long does it take for reCAPTCHA to reset?

If you change your domain, update reCAPTCHA settings It can take up to 30 minutes for domain updates to take effect.


3 Answers

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);
like image 57
Vinay Modi Avatar answered Nov 12 '22 08:11

Vinay Modi


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.

like image 43
Haradzieniec Avatar answered Nov 12 '22 09:11

Haradzieniec


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.

like image 1
Giampaolo Ficorilli Avatar answered Nov 12 '22 10:11

Giampaolo Ficorilli