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

9

38

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.

Medievalist answered 15/1, 2017 at 20:28 Comment(0)
M
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>
Medievalist answered 17/1, 2017 at 10:12 Comment(13)
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.Apposite
@Apposite this code is running on our website and it does submit oO grecaptcha.execute() submits it WHEN the reCAPTCHA form test is correctly done.Medievalist
@Apposite read Google reCaptcha documentation carefully, the callback will be called when the challenge is passed only, so the code above is just fine.Calciferol
@JulioGuerra you're right, but it doesn't use browser validation default unfortunately. I guess mine below does. Try to check it out.Apposite
@MohammadWalid if I remember well, you're not wrong, but the code doesn't use browser validation defaults, mine below does. That's something you should consider testing.Apposite
It does and it's even live for several months now. The built-in validation is performed when the user clicks on the submit button. Since this is built-in, there's nothing else to do and there's no standard API to trigger it programmatically.Medievalist
formSubmit() has to be empty?!Oneiric
@Oneiric 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).Medievalist
Why are you using grecaptcha.reset();? Is that for Ajax forms only?Haplo
It simply resets recaptcha's internal state to be able to re-validate the form. In case of submit failures, due to recaptcha failure or any service-side form validation, you need to be able to submit the form possibly several times.Medievalist
when I use this code I get an error: reCAPTCHA placeholder element must be empty. Googled it and people suggest that it's loading twice the api.js but I can assure that my code isn'tCorin
Something is not working for me, plus I don't even understand. If formSubmit is empty, then nothing happens. And if formSubmit has a javascript submit call to the form, it creates an endless loop. What am I doing wrong that this code works for you and doesn't for me?Teakwood
this is the right answer, with the missing part: https://mcmap.net/q/410924/-how-to-run-recaptcha-only-if-html5-validation-has-passedTeakwood
M
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();
    });
  })
Matchless answered 22/3, 2017 at 11:3 Comment(0)
E
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>
Emelina answered 4/5, 2017 at 15:28 Comment(0)
E
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>
Exostosis answered 6/8, 2020 at 19:32 Comment(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); });Competitive
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?Exostosis
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.Competitive
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.Exostosis
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.Competitive
The execute method should not be called until the form is submitted. So if console logging the token after the line grecaptcha.execute("RECAPTCHA_SITE_KEY", { action: 'submit' }).then(function(token) { does not log at the time a valid form is attempted to submit, after the execute promise, then something else is wrong related to the setup of the API key, or the way the formSubmitBtn method is being called. Are you on a domain that is whitelisted in your Google apps API? Are you getting any other errors thrown in the console?Exostosis
B
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.

Barretter answered 5/2, 2019 at 12:25 Comment(0)
G
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

Guajardo answered 14/7, 2022 at 18:16 Comment(0)
A
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.

Apposite answered 10/3, 2017 at 22:10 Comment(2)
exactly the same flowMedievalist
No, if I remember well, your answer above doesn't use browser validation defaults, mine does. Consider testing both before leaving -1. Thanks.Apposite
M
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

Modernity answered 19/8, 2018 at 6:49 Comment(0)
G
0

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>
Grangerize answered 18/7, 2024 at 16:27 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.