Search code examples
htmlformsvalidationrecaptchainvisible-recaptcha

How do I perform HTML form validation with Google ReCaptcha V2 Invisible AND Multiple Forms?


I have 2 forms on a page that both need Google ReCaptcha V2 Invisible. I have this working perfectly using the following code.

In the Header:

<script src='https://www.google.com/recaptcha/api.js?onload=myCallBack&render=explicit' async defer></script>

Here is the form. Pretty simple. You can see the ReCaptcha is tied to the button, and that it has a unique callback and ID.

<form id="subForm" action="form-processing.php" method="POST">         
    <input placeholder="Name" id="name" name="fname" type="text" required/>
    <input placeholder="Email" id="email" name="email" type="text" required/>
    <textarea placeholder="Comments" id="comments" cols="30" name="comments" rows="6" required></textarea>

    <button class="g-recaptcha form-submit-button" data-sitekey="MySiteKey" data-callback="captchaSubmit2" id="recaptcha2">Submit</button>
</form>

When the ReCaptcha api in the header loads, and fires off the onload callback (myCallBack) the following code is called to render the ReCaptcha on each of the form's buttons.

var recaptcha1;
var recaptcha2;
var myCallBack = function() {
    //Render recaptcha1 on element with ID "recaptcha1"
    recaptcha1 = grecaptcha.render('recaptcha1', {
      'sitekey' : 'MySiteKey',
      'theme' : 'light'
    });
    
    //Render recaptcha2 on element with ID "recaptcha2"
    recaptcha2 = grecaptcha.render('recaptcha2', {
      'sitekey' : 'MySiteKey',
      'theme' : 'light'
    });
};

And finally, when a submit button is clicked, ReCaptcha processes, and uses the callback specified on the button to trigger the actual form submission.

function captchaSubmit1(data) {
    document.getElementById("mainForm").submit();
}
function captchaSubmit2(data) {
    document.getElementById("subForm").submit();
}

On the backend in my server-side processing I am using the following code to verify the ReCaptcha.

<?php
    // reCaptcha info
    $secret = "MySecretKey";
    $remoteip = $_SERVER["REMOTE_ADDR"];
    $url = "https://www.google.com/recaptcha/api/siteverify";

    // Form info
    $first = $_POST["first"];
    $last = $_POST["last"];
    $response = $_POST["g-recaptcha-response"];

    // Curl Request
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($curl, CURLOPT_POSTFIELDS, array(
        'secret' => $secret,
        'response' => $response,
        'remoteip' => $remoteip
        ));
    $curlData = curl_exec($curl);
    curl_close($curl);

    // Parse data
    $recaptcha = json_decode($curlData, true);
    if ($recaptcha["success"])
    { 
       /// MY DATA VALIDATION & EMAILING OF FORM DATA ///
    }else{
        header('Location: /thank-you-for-contacting-us/?gvx');
        exit;
    }
?>

If the form validation fails due to Google ReCaptcha verify failure, it simply redirects to the thank you page with a code so I know it was the recaptcha that failed. There is a separate code if the form field data fails validation. Just a way to help me see what is going on.

OK! so now the problem is that these forms do not perform the HTML client-side field validation. I have tried to incorporate this solution with no luck: How to run reCaptcha ONLY if HTML5 validation has passed?

These bits of code are the main take-aways from that article. I replaced the submit button:

Before:

<button class="g-recaptcha form-submit-button" data-sitekey="MySiteKey" data-callback="captchaSubmit2" id="recaptcha2">Submit</button>

After:

<button class="form-submit-button" type="submit">Submit</button>

And I added this div into the form just above the submit button:

<div id="recaptcha2" class="g-recaptcha" 
data-sitekey="MySiteKey"
data-size="invisible"
data-callback="captchaSubmit2">

And these are the new handlers for the form submit:

$('#mainForm').submit(function (event) {
    if (!grecaptcha.getResponse()) {
        event.preventDefault(); //prevent form submit
        grecaptcha.reset();
        grecaptcha.execute();
    } 
});
$('#subForm').submit(function (event) {
    if (!grecaptcha.getResponse()) {
        event.preventDefault(); //prevent form submit
        grecaptcha.reset();
        grecaptcha.execute();
    } 
});

So the final flow here is that page loads the api, the api calls the recaptcha render that renders the recaptcha on the g-recaptcha div, the form is submitted, the default behavior is prevented allowing the HTML validation to function (Which it does), and when the form actually does submit, it triggers the above function to call grecaptcha.execute(); which processes, and when it completes, calls the callback function tied to it (captchaSubmit2) which does the real final submit. Then my server-side script is failing google validation, and sadness ensues...

I've spent two days beating on this now, with the usual "Is it done yet?" echoing from above. I am so close I can taste it, but I am obviously missing something.

I just need Google ReCaptcha V2 Invisible working on 2 forms on the same page with HTML form validation for required fields.


Solution

  • After a lot of searching, I finally found a solution that works and is rather elegant in the fact that it supports multiple forms on a single page, and is dynamic. It searches the page and attaches the recaptcha to any matching tagged divs. It should support as many forms as you like on one page.

    This is the tag it attaches to:

    <div class="recaptcha-holder"></div>
    

    I am still unsure why my own implementation did not work. Every time I had recaptcha attached to multiple forms, the server-side processing would fail, and debugging showed it was not receiving recaptcha data.

    Just a note, this is being implemented on a WordPress site, so I am adding the javascript to my existing js include that I put my custom code in.

    This code is the last entry on this question (Implement the new Invisible reCaptcha from Google) posted by Mch (https://stackoverflow.com/users/7327512/mch). All thanks goes to them for the front-end solution. Funny enough it was posted at the very end of 2016.

    Mch's Front-End Example

    <!DOCTYPE html>
    <html>
    <body>
    
    <form action="" method="post">
        <input type="text" name="first-name-1"> <br />
        <input type="text" name="last-name-1"> <br />
    
        <div class="recaptcha-holder"></div>
    
        <input type="submit" value="Submit">
    </form>
    
    <br /><br />
    
    <form action="" method="post">
        <input type="text" name="first-name-2"> <br />
        <input type="text" name="last-name-2"> <br />
    
        <div class="recaptcha-holder"></div>
    
        <input type="submit" value="Submit">
    </form>
    <script src="https://www.google.com/recaptcha/api.js?onload=renderGoogleInvisibleRecaptcha&render=explicit" async defer></script>
    </body>
    </html>
    

    Mch's Javascript (Added to my existing JS include)

      var renderGoogleInvisibleRecaptcha = function() {
        for (var i = 0; i < document.forms.length; ++i) {
          var form = document.forms[i];
          var holder = form.querySelector('.recaptcha-holder');
          if (null === holder){
            continue;
          }
    
          (function(frm){
    
            var holderId = grecaptcha.render(holder,{
              'sitekey': 'CHANGE_ME_WITH_YOUR_SITE_KEY',
              'size': 'invisible',
              'badge' : 'bottomright', // possible values: bottomright, bottomleft, inline
              'callback' : function (recaptchaToken) {
                HTMLFormElement.prototype.submit.call(frm);
              }
            });
    
            frm.onsubmit = function (evt){
              evt.preventDefault();
              grecaptcha.execute(holderId);
            };
    
          })(form);
        }
      };
    

    And finally my Back-End (server side) processing:

    <?php
        // reCaptcha info
        $secret = "MySecretKey";
        $remoteip = $_SERVER["REMOTE_ADDR"];
        $url = "https://www.google.com/recaptcha/api/siteverify";
    
        // Form info
        $first = $_POST["first"];
        $last = $_POST["last"];
        $response = $_POST["g-recaptcha-response"];
    
        // Curl Request
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_POST, true);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_POSTFIELDS, array(
            'secret' => $secret,
            'response' => $response,
            'remoteip' => $remoteip
            ));
        $curlData = curl_exec($curl);
        curl_close($curl);
    
        // Parse data
        $recaptcha = json_decode($curlData, true);
        if ($recaptcha["success"])
        { 
            // MY DATA VALIDATION & EMAILING OF FORM DATA
        }else{
            // Google ReCaptcha Failed to Verify - Send to False Positive
            header('Location: /thank-you-for-contacting-us/?gvx');
            exit;
        }
    ?>