How to solve Google v3 reCaptcha timeout?
Asked Answered
M

6

14

We have a PHP form that is several tabs and times-out on the reCaptcha. Everything is done in one page and it works perfectly fine IF the form is completed in <3 minutes.

The idea of a solution is to move the form processing and reCaptcha to a secondary page for processing.

The problem is that the form page polls the google service for reCaptcha and collects a token value to a hidden field.

<input type="hidden" name="recaptcha_response" id="recaptchaResponse">

The problem is how to request this token on the server side processing page? Here is the code used on the client side form page. I need to somehow regenerate the token value to apply as :

$recaptcha_response

Here is the working version on the form page. It's easy to remove the requirement on Posting the token from the form page, just not sure how to regenerate the token to use on the server side page.

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['recaptcha_response'])) {

// Build POST request:
$recaptcha_url = 'https://www.google.com/recaptcha/api/siteverify';
$recaptcha_secret = RECAPTCHA_SECRET_KEY;
$recaptcha_response = $_POST['recaptcha_response'];
$remoteip = $_SERVER['REMOTE_ADDR'];

// Make and decode POST request:
$recaptcha = file_get_contents($recaptcha_url . '?secret=' . $recaptcha_secret . '&response=' . $recaptcha_response. '&remoteip='.$remoteip);
$recaptcha = json_decode($recaptcha);

// Take action based on the score returned:
if ($recaptcha->score >= 0.5) {

EDIT TO ADD: Would making the initialization of the reCaptcha until Submit delay the timing issue since this seems to be an option:

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

"2. Call grecaptcha.execute on an action or when the page loads"

Meghanmeghann answered 20/3, 2019 at 0:19 Comment(4)
Your server side looks ok etc .., but the client side will require another execution of grecaptcha.ready( function() { grecaptcha.execute( ... ); })Regalado
Your saying that the solution is to not move to a secondary page (which works now if <~3mins) but re-execute the sending and receiving of the token prior to posting the Submit?Meghanmeghann
it depends on when you need the captcha information, if you need it and it's expired - then the only way to get it again is to have the client (browser) request a token - and the server to verify it. personally, once I've verified the client - I don't need to reverify it - even after 10 minutes.Regalado
So rather than on Submit check the client on page load which then removes the timeout possibility. That is another concept and it makes good sense!Meghanmeghann
P
12

We've been running into this recently, too. I found this solution on GitHub and it's been working well for us.

This has the benefit of only asking for a token when the user interacts with the page (such as submitting a form) instead of having to keep asking for one.

<script>
  grecaptcha.ready(function() {
      document.getElementById('contactform').addEventListener("submit", function(event) {

        event.preventDefault();

        grecaptcha.execute('mykey', {action: 'homepage'}).then(function(token) {
           document.getElementById("googletoken").value = token; 
           document.getElementById('contactform').submit();
        });        
      }, false);

  });
</script>
Perambulator answered 9/6, 2020 at 20:38 Comment(6)
Thanks, I'll give your method a try next time we code something similar.Meghanmeghann
Just had the same issue and implemented the method using a named function and listening for "submit" and it worked perfectly.Meghanmeghann
rifton007 commented on 13 Feb @iwasherefirst2 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.Purplish
That's true, so in that case you can just show some kind of "working" indicator right after event.preventDefault(); so the user knows that the form is being submitted, which should take care of most concerns.Perambulator
I don't have a googletoken element. Where should I put the token?Seraphim
@PetarVasilev That's dependent on your application. That's a field that gets submitted to the server to be validated.Perambulator
P
25

If you do not wish to change your code too much then an alternative approach would be to wrap the reCaptcha JavaScript in a named function, set up an interval and poll that function prompting reCaptcha to add a new token to your form element 10 seconds before each two minute token expiry:

function getReCaptcha(){
    grecaptcha.ready(function() {
       ...
     });
 }

 getReCaptcha();  // This is the initial call
 setInterval(function(){getReCaptcha();}, 110000);
Phonotypy answered 8/11, 2019 at 8:1 Comment(3)
This should be marked as the correct answer for this issue, though I set my setInterval to 90*1000 milliseconds, as the Google documentation states the the timeout of the token is 120 seconds.Normanormal
Good answer. Although, your example shows 2.5 minutes not 3.Dubose
Thanks for the comments about the life of a reCaptha token. I had taken the OP's word that the life was 3 minutes and right enough Google's documentation say that the token only has a life of 2 minutes. I've updated my answer to fetch a new token every 110 seconds rather than every 150 seconds.Phonotypy
P
12

We've been running into this recently, too. I found this solution on GitHub and it's been working well for us.

This has the benefit of only asking for a token when the user interacts with the page (such as submitting a form) instead of having to keep asking for one.

<script>
  grecaptcha.ready(function() {
      document.getElementById('contactform').addEventListener("submit", function(event) {

        event.preventDefault();

        grecaptcha.execute('mykey', {action: 'homepage'}).then(function(token) {
           document.getElementById("googletoken").value = token; 
           document.getElementById('contactform').submit();
        });        
      }, false);

  });
</script>
Perambulator answered 9/6, 2020 at 20:38 Comment(6)
Thanks, I'll give your method a try next time we code something similar.Meghanmeghann
Just had the same issue and implemented the method using a named function and listening for "submit" and it worked perfectly.Meghanmeghann
rifton007 commented on 13 Feb @iwasherefirst2 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.Purplish
That's true, so in that case you can just show some kind of "working" indicator right after event.preventDefault(); so the user knows that the form is being submitted, which should take care of most concerns.Perambulator
I don't have a googletoken element. Where should I put the token?Seraphim
@PetarVasilev That's dependent on your application. That's a field that gets submitted to the server to be validated.Perambulator
H
4
<script type="text/javascript">
function grecaptcha_execute(){
    grecaptcha.execute('RECAPTCHA_SITE_KEY', {action: 'homepage'}).then(function(token) {
        document.getElementById('recaptchaResponse').value=token;
    });
}
grecaptcha.ready(function() {
    grecaptcha_execute();
});
</script>

<?php
if ($recaptcha->success) {
    echo '<p style="color: #0a860a;">CAPTCHA was completed successfully!</p>';
}else{
    $error_codes = 'error-codes';
    if (isset($Retorno->$error_codes) && in_array("timeout-or-duplicate", $Retorno->$error_codes)) {
        $captcha_msg = "The verification expired due to timeout, please try again.";
    ?>
    <script>grecaptcha_execute();</script>
    <?php
    } else {
        $captcha_msg = "Check to make sure your keys match the registered domain and are in the correct locations.<br> You may also want to doublecheck your code for typos or syntax errors.";
    }
    echo '<p style="color: #f80808;">reCAPTCHA error: ' . $captcha_msg . '</p>';
}
?>
Hygienics answered 27/3, 2019 at 12:16 Comment(0)
L
1

Load reCaptcha API js and Jquery (if you use jquery code):

<script src="https://www.google.com/recaptcha/api.js?render=SITE_KEY"></script>
<script  src="https://code.jquery.com/jquery-3.4.1.min.js"></script>

reCaptcha Render Javascript:

  <script type="text/javascript">
     $(document).ready(function(){
        setInterval(function(){
        grecaptcha.ready(function() {
            grecaptcha.execute('SITE_KEY', {action: 'home'}).then(function(token) {
                $('#recaptchaResponse').val(token);
            });
        });
        }, 3000); //you can set timeout for expired.
     });
     
  </script>

Site Varify:

<?php 
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit'])) {
        if(isset($_POST['recaptcha_response']) && !empty($_POST['recaptcha_response'])){
            $recaptcha_url = 'https://www.google.com/recaptcha/api/siteverify';
            $recaptcha_secret = 'SECRET_KEY';
            $recaptcha_response = $_POST['recaptcha_response'];
            $response = file_get_contents($recaptcha_url . '?secret=' . $recaptcha_secret . '&response=' . $recaptcha_response);
            $recaptcha = json_decode($response);
            if ($recaptcha->success == true && $recaptcha->score >= 0.5) {
                echo $response;
                echo '<br>Form Submitted Successfully';
            } else { 
                echo $response;
                echo '<br>reCaptcha varification failed';
            }
        }else {
            echo 'reCaptcha is empty';
        } 
    }
?>

HTML Form:

<form method="POST">
    <label class="label">Name</label>
    <input type="text" name="name" class="input" placeholder="Name" required>
    <button type="submit" name="submit" class="button is-link">Send Message</button>
    <input type="hidden" name="recaptcha_response" id="recaptchaResponse">
</form>
Linen answered 22/6, 2020 at 19:35 Comment(0)
A
0

Ignore everything else here. Reasons:

  1. If you just generate the token on page load, it's going to time out after 2 minutes, which means users will not be happy about it
  2. Refreshing tokens will cost you (there's free tier but it costs after using X requests)

Solution: Generate the token at the time of form submission! https://jsfiddle.net/skobaljic/rL6fux5w/2/

var webQueryForm = $('#webQuery');
webQueryForm.submit(function(event) {
  e.preventDefault();
  grecaptcha.execute('MYSITEKEY(PUBLIC)', {
     action: 'contact'
     }).then(function(token) {
     $('#recaptchaToken').val(token);
    webQueryForm.submit();
  });
});
Alethaalethea answered 4/10, 2021 at 21:48 Comment(0)
U
0

When you use grecaptcha.execute(), it does not resolve an issue. Because execute() can also throw an error. To solve this issue you should put execute inside try-catch block. For example:

grecaptcha.ready(function() {
    try {
      grecaptcha.execute('RECAPTCHA_SITE_KEY', {action: 'submit'})
        .then(function(token) {
            submitYourForm(token);
        })
    } catch (error) {
        yourRetryBlockHere();
    }
});
Ultimate answered 16/10, 2021 at 11:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.