Invisible google Recaptcha and ajax form

Kuqa picture Kuqa · Mar 17, 2017 · Viewed 11.9k times · Source

I have an ajax form:

  <form id="my_form">
    <input type="text" id="field1" />
    <input type="submit" value="submit" />
  </form>

And js code:

document.getElementById("my_form").onsubmit = function(e) {
  e.preventDefault();

  var xhr = new XMLHttpRequest();
  //.............. send request to a server

In the documentation it assumes that a form is a normal form, not ajax. How exactly should I integrate invisible reCaptcha to my ajax forms? For example:

  <form id="my_form">
    <input type="text" id="field1" />
    <div class="g-recaptcha" data-sitekey="12345" data-callback="????></div>
    <input type="submit" value="submit" />
  </form>

And, in particular, what should I specify for "data-callback" handler? Again, in the documentation it data-callback submits a form, but a normal form, whereas mine is ajax. Do I need "data-callback" at all? Shouldn't I instead call recaptcha inside my handler? How?

There're "render", "getResponse" and "execute". Which one should I use? It's not clear from the documentation.

Answer

codneto picture codneto · Mar 19, 2017

I agree that "invisible" recaptcha documentation is not comprehensive enough. I had to spend some time digging thru code example and documentations of "visible" recaptcha before understanding how to work with this.

Let talk about the recaptcha API first:

grecaptcha.render(htmlEl, options, inherit) is JS API method of rendering the captcha HTML on the page. By default recaptcha script will try to find any element with class="g-recaptcha and try to render immediately, but this behavior can be overridden by appending ?render=explicit query param to recaptcha script src url. You also may want to render the recaptcha html on demand using this api when your recaptcha .g-recaptcha element gets attached to DOM at a point later than when script was loaded. This api returns an ID value that can be passed to other api methods, but if not passed, those api's lookup and reference first repcaptcha on page.

grecaptcha.getResponse(optional_id) returns the token. If token is empty string, it means user has not been validated yet i.e. user hasn't completed the captcha challenge.

grecaptcha.execute(optional_id) api triggers the recaptcha challenge on-demand programmatically. This api is only applicable to "invisible" recaptcha. Visible recaptcha challenges are triggered when user clicks the recaptcha module.

grecaptcha.reset(optional_id) will reset a challenge i.e. it must be done each time server fails to validate the token with recaptcha api server (because tokens are one time use), but depending on your implementation, you may decide to reset any time.

Now, lets talk about data-callback:

data-callback is an attribute where you can pass a name of global namespaced function, i.e. some function which is accessible as window['nameOfFunction']. This callback will get called each time user is successfully validated with a token value that you will eventually be passing to server. This is same token that is returned by grecaptcha.getResponse() so technically you do not need this function at all. But it can serve as callback to let you know user has passed verification in case you need to update UI or something.

If for some reason you do not want this callback to be accessible from window namespace, you can pass this method in options object with callback key to grecaptcha.render(). NOTE: options.callback can take a string value which is equivalent to passing data-callback attribute in HTML, i.e. is must be a function in window namespace. But options.callback can take a "function" value as well.


Now some sample code:

HTML

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

JS

window.onScriptLoad = function () {
    // this callback will be called by recaptcah/api.js once its loaded. If we used
   // render=explicit as param in script src, then we can explicitly render reCaptcha at this point

    // element to "render" invisible captcha in
    var htmlEl = document.querySelector('.g-recaptcha');

    // option to captcha
    var captchaOptions = {
      sitekey: '6Lck',
      size: 'invisible',
      // tell reCaptcha which callback to notify when user is successfully verified.
      // if this value is string, then it must be name of function accessible via window['nameOfFunc'], 
      // and passing string is equivalent to specifying data-callback='nameOfFunc', but it can be
      // reference to an actual function
      callback: window.onUserVerified
  };

    // Only for "invisible" type. if true, will read value from html-element's data-* attribute if its not passed via captchaOptions
    var inheritFromDataAttr = true;

    // now render
    recaptchaId = window.grecaptcha.render(htmlEl, captchaOptions, inheritFromDataAttr);
};

// this is assigned from "data-callback" or render()'s "options.callback"
window.onUserVerified = function (token) {
    alert('User Is verified');
    console.log('token=', token);
};


// click handler for form's submit button
function onSubmitBtnClick () {      
  var token =   window.grecaptcha.getResponse(recaptchaId);

  // if no token, mean user is not validated yet
  if (!token) {
     // trigger validation
     window.grecaptcha.execute(recaptchaId);
     return;
  }

  var xhrData = {
    'g-recaptcha-response': token
    // more ajax body/data here
  };

  // proceed with appending more ajax call data to xhrData and then rest of ajax call process
  // var xhr = new XMLHttpRequest();
  // ... ... .... ... ... 
}