37

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.

0

8 Answers 8

45

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>

13
  • 4
    Using this code, the form will never submit, because it is preventing the default action (which is submit). If you wanna submit, you gotta not prevent the action due to a condition, like my answer below, please check there. Mar 10, 2017 at 21:04
  • 1
    @giovannipds this code is running on our website and it does submit oO grecaptcha.execute() submits it WHEN the reCAPTCHA form test is correctly done. Mar 14, 2017 at 21:56
  • 2
    @JulioGuerra you're right, but it doesn't use browser validation default unfortunately. I guess mine below does. Try to check it out. Jun 9, 2017 at 18:25
  • 3
    formSubmit() has to be empty?!
    – Black
    Nov 20, 2017 at 22:18
  • 2
    @Black not necessarily. The documentation says "The user's response, g-recaptcha-response, will be the input for your callback function." I changed the example to include it instead of ignoring it (because you can also read it from an hidden input in the form, once validated). Nov 21, 2017 at 13:57
6

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();
    });
  })
0
6

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>

3

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>
6
  • I stumbled across your post for a similar solution. Is this intended to be purely client-side? The token doesn't appear to be populated. Secondly, is there any reason why you chose not to use addEventListener e.g. document.addEventListener('DOMContentLoaded', function () { document.getElementById('html-submitt') .addEventListener('submit', formSubmitBtn); });
    – Motivated
    Jan 10, 2021 at 3:47
  • This is intended to be flexible. There is no server dependency, if that's what you're asking. Of course you'll need to use your private api keys to send the response token to google to get the recaptcha values. Did you replace RECAPTCHA_SITE_KEY with your specific key? I'm running this code in a production environment and it is working for me. I do not see a reason to add event listeners and consume resources and risk memory leaks from not removing event listeners, etc (depending on the usage). The one function will support unlimited forms. Happy to help further. Code snippet?
    – PigBoT
    Jan 10, 2021 at 20:59
  • The code is being run verbatim with an updated recaptcha key. When the form is submitted, it performs the native HTML5 validation (which is a plus). It then captures the values e.g. name. It does not however return a token in the hidden input. It's blank. The reason for using event listeners is because inline click events are blocked by content security policies.
    – Motivated
    Jan 10, 2021 at 21:59
  • I'm not a CSP expert. Lots of questions--but not the place in these comments. :P It does sound like a reasonable workaround for your situation. There are so many places that could go wrong with the change, notably around the $event. Is it still coming through as expected? Is token created from the execute method? Is it attaching the token value to the correct form? If you make a codepen or something similar I could help debug.
    – PigBoT
    Jan 11, 2021 at 15:28
  • I'm unsure if Codepen or anything similar has the option to implement a CSP policy as this tends to be server side. Happy to share the content security policy if you like. Do you mean is it coming through on load? If yes, no there is no token on load. No token is generated either when the form is submitted. The hidden value is empty.
    – Motivated
    Jan 11, 2021 at 21:34
2

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.

0
1

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

0
 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

0

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.

2
  • exactly the same flow Mar 14, 2017 at 22:02
  • No, if I remember well, your answer above doesn't use browser validation defaults, mine does. Consider testing both before leaving -1. Thanks. Jun 9, 2017 at 18:22

Not the answer you're looking for? Browse other questions tagged or ask your own question.