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
Load the JavaScript api with your sitekey
Call grecaptcha.execute on an action or when the page loads // we choose when the page loads, OK?
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.
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.
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.
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.
If you change your domain, update reCAPTCHA settings It can take up to 30 minutes for domain updates to take effect.
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);
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With