Julio Guerra
Julio Guerra

Reputation: 5661

How can I process HTML form validation before reCAPTCHA's validation?

I integrated the new hidden reCAPTCHA (v2) framework which by default verifies the user with the click event of the submit button. But this event is triggered before the built-in HTML form validation. I am looking for a way to make it in the expected order: form validation first, reCAPTCHA after.

Upvotes: 38

Views: 30967

Answers (9)

Jon Green
Jon Green

Reputation: 51

Since September 2022, all major browsers have supported the requestSubmit() method (Safari was the latecomer, in case anybody is interested) which does trigger the browser's built-in constraint validation and raises the submit event.

So we can now just modify Recaptcha's recommended code by changing its call to the submit() method to a call to the requestSubmit() method:

 <script>
   function onSubmit(token) {
     document.getElementById("demo-form").submit();
   }
 </script>

becomes

 <script>
   function onSubmit(token) {
     document.getElementById("demo-form").requestSubmit();
   }
 </script>

Upvotes: 2

jessieloo
jessieloo

Reputation: 1872

This solution is similar to solution by @PigBoT but with the addition of reportValidity() and is using ReCAPTCHA v3

Credit to https://github.com/ambethia/recaptcha/issues/302#issuecomment-621794131

<script src="https://www.google.com/recaptcha/api.js"></script>
<script type="text/javascript">
    function contactOnSubmit(token) {
        var contactForm = document.getElementById('contactUs');
        if(contactForm.checkValidity()) {
            //SERVER SIDE VALIDATION here, 
            //on success, contactForm.submit();  
        } else {
            grecaptcha.reset();
            contactForm.reportValidity();
        } 
    }
</script>

Form (id="contactUs")

<button class="g-recaptcha" data-sitekey="..." data-callback="contactOnSubmit" data-action="submit">Submit</button>

"Can I Use" site currently reports 97% uses have support for checkValidity() https://caniuse.com/?search=checkValidity

Upvotes: 1

PigBoT
PigBoT

Reputation: 486

Here's my solution.

  • Uses reCaptcha v3 (invisible) docs
  • Uses native HTML5 form validation
  • Uses pure JS
  • Uses standard POST processing (can be modified to AJAX)

Add as many forms as needed, just change the 'UNIQUE_FORM_ID' in the two places, and update the POST_URL for the form. Ensure you use your own key in the locations of 'RECAPTCHA_SITE_KEY'.

<form id="UNIQUE_FORM_ID" method="post" action="POST_URL">
    <!-- ** Notice ** this hidden input field that will later send our g-recaptcha token back to our server -->
    <input type="hidden" name="g-recaptcha-response" value="">
    <!-- Add other hidden nonce fields -->

    <!-- Required field -->
    <input name="fullname" type="text" placeholder="Full Name" required>

    <!-- Submit button -->
    <!-- ** Notice ** the 'form' attribute; using SAME value as it's parent's form id, above. -->
    <!-- ** Notice ** the 'onclick' attribute; be sure to pass event -->
    <button type="submit" form="UNIQUE_FORM_ID" onclick="formSubmitBtn(event)">Send</button>
</form>

<!-- Only add scripts once -->
<!-- ** Notice ** to manually call grecaptcha, our site key must be included when loading api.js using the 'render' query param -->
<script src="https://www.google.com/recaptcha/api.js?render=RECAPTCHA_SITE_KEY"></script>
<script>
    /**
     * Handles form submissions for Google recaptcha v3.
     * Allows for HTML5 form validation to complete before processing.
     */
    function formSubmitBtn($event) {
        /**
         * Checks the validity of the form.
         * Return if invalid; HTML5 validation errors should display.
         */
        if (!$event.target.form.checkValidity()) {
            return;
        }
        /**
         * Form is client-side valid; taking over the remainder of processing.
         */
        $event.preventDefault();
        grecaptcha.ready(function() {
            grecaptcha.execute("RECAPTCHA_SITE_KEY", { action: 'submit' }).then(function(token) {
                /**
                 * Adds the token g-recaptcha-response token to our hidden form element.
                 * ** Notice ** we our referencing the specific form's input element by name here (do not use IDs).
                 */
                $event.target.form.elements['g-recaptcha-response'].value = token;
                /**
                 * Use the form API directly to submit the form.
                 */
                $event.target.form.submit();
            });
        });
    }
</script>

Upvotes: 3

USD Matt
USD Matt

Reputation: 222

I had this problem as the default method seems to override the html5 form validation. I also wanted all code to be generic rather than hard coding any functions/element names. In the end I came up with the following code using the v3 api -

HTML

<form method="post" action="?" class="ui-recaptcha" name="my_form_name">
   ...
   <input type="submit" value="Submit">
</form>
<script src="//www.google.com/recaptcha/api.js?render={key}" async defer></script>

Javascript (I'm using jQuery but would be fairly easy to adapt to vanilla js)

$('.ui-recaptcha').submit(e => {

    var form = e.target;

    if( $(form).data('recaptcha-done') )
        return;

    e.preventDefault();
    grecaptcha.execute('{key}', {'action': $(form).attr('name')}).then(token => {

        $(form).append($('<input>').attr({'type': 'hidden', 'name': 'g-recaptcha-response', 'value': token}));
        $(form).data('recaptcha-done', true);
        $(form).submit();
    });
});

I found that just calling submit as in some examples above caused a loop for me, which would make sense seeing as the recaptcha handler runs on the submit event.

This runs recaptcha for any ui-recaptcha form, passes the form name attribute as the action which can be seen in reCaptcha console, and then inserts the token into the form. Once run it sets a data attribute on the form so the recursive call to submit doesn't try to run recaptcha again.

Upvotes: 2

giovannipds
giovannipds

Reputation: 3479

I was wanting the same behavior, but using the new recaptcha, the invisible one. After looking at some code and testing some stuff, I got into this. The main difference is that this uses the default browser validation as well:

var contact_form;
$(function() {
    contact_form = $('#contact-form');
    contact_form.submit(function (event) {
        if ( ! contact_form.data('passed')) {
            event.preventDefault();
            grecaptcha.execute();
        }
    });
});
function sendContactForm(token) {
    contact_form.data('passed', true);
    contact_form.submit();
}

It basically stores the jquery form object in a global var, including, it uses sendContactForm as the callback, but when called by the recaptcha, it sets a data var named passed, which allows the form to not be prevented. It's exactly the same behavior as recaptcha would normally do, but with that condition.

Update: re-looking at my code right reminds me that it probably needs a way to restore data passed to false after grecaptcha's execution. Consider that if you'll implement this.

Upvotes: 0

Zoran
Zoran

Reputation: 2793

 let siteKey = "...";
 $("form").submit(function (eventObj) {
        var myForm = this;
        eventObj.preventDefault();
        grecaptcha.execute( siteKey, {
            action: "submit"
        })
            .then(function (token) {
                $('<input />').attr('type', 'hidden')
                    .attr('name', "g_recaptcha_response")
                    .attr('value', token)
                    .appendTo(myForm);
                myForm.submit();
            });
    });

This will execute recapcha, wait for response, add hidden attribute g_recaptcha_response to any form when browser try to submit it and then actually submit it. You need global variable siteKey

Upvotes: 0

Julio Guerra
Julio Guerra

Reputation: 5661

You have to do it programmatically thanks to a new v2 grecaptcha method: grecaptcha.execute() so that recaptcha doesn't replace the button's default click event which was preventing the default HTML5 form validation.

The event path is:

  1. Submit button click event: browser built-in form validation
  2. Form submit event: call grecaptcha.execute()
  3. reCAPTCHA callback: submit the form

$('#form-contact').submit(function (event) {
    event.preventDefault();
    grecaptcha.reset();
    grecaptcha.execute();
  });

function formSubmit(response) {
  // submit the form which now includes a g-recaptcha-response input
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://www.google.com/recaptcha/api.js"></script>
<form action="?">
  <div class="g-recaptcha" 
       data-sitekey="your-key"
       data-size="invisible"
       data-callback="formSubmit">
  </div>
  <button type="submit">Submit</button>
</form>

Upvotes: 45

Maxime
Maxime

Reputation: 61

Hi got a working solution here. Working with invisible Recaptcha.

jQuery(document).ready(function() {
    var commentform = jQuery("#commentform");
    commentform.on("click", "#submit-comment", function(e) {
      if(commentform[0].checkValidity()) {
        e.preventDefault();
        grecaptcha.execute();
      }
    });
});

function submitCommentForm(data) {
    document.getElementById("commentform").submit();
}
<form action="blaba.php" method="post" id="commentform" class="comment-form">
  <div class="form-submit">
    <div data-callback="submitCommentForm" data-sitekey="yourkey" class="g-recaptcha" data-size="invisible">
    <button id="submit-comment">Leave a comment</button>
  </div>
</form>

Upvotes: 6

Byscripts
Byscripts

Reputation: 2588

Here is my solution to get HTML5 validation + Invisible recaptcha:

HTML:

<form id="my-form">
    <!-- Your form fields ... -->
    <div class="g-recaptcha"
        data-sitekey="..."
        data-callback="submitMyForm"
        data-size="invisible">
    </div>
    <button type="submit">Submit</button>
</form>

JS:

var myForm = $('my-form');

function submitMyForm () {
    myForm.trigger('submit', [true]);
}

$(function () {
    myForm.on('submit', function (e, skipRecaptcha) {
        if(skipRecaptcha) {
            return;
        }

        e.preventDefault();
        grecaptcha.execute();
    });
  })

Upvotes: 6

Related Questions