3

Here is an example code from Google about how to safely initialize its recaptcha service https://developers.google.com/recaptcha/docs/loading

<script async src="https://www.google.com/recaptcha/api.js"></script>

<script>    
  if(typeof grecaptcha === 'undefined') {
    grecaptcha = {};
  }

  grecaptcha.ready = function(cb){
    //How is it possible for grecaptcha to be undefined here?
    if(typeof grecaptcha === 'undefined') {
      
      const c = '___grecaptcha_cfg';
      window[c] = window[c] || {};
      (window[c]['fns'] = window[c]['fns']||[]).push(cb);
    } else {
      cb();
    }
  }

  grecaptcha.ready(function(){
    grecaptcha.render("container", {
      sitekey: "ABC-123"
    });
  });
</script>

As you can see grecaptcha.ready is a function and it again checks if(typeof grecaptcha === 'undefined'). But how can grecaptcha be undefined at the time when grecaptcha.ready is called? As I understand grecaptcha has already been initialized as a property of global object and it's value is {} hence it's type should be "object"

3
  • It cannot be undefined in ready Commented Dec 13, 2021 at 20:41
  • Naturally, it doesn't make a whole lot of sense but it would seem like a huge oversight on Google's part if it was wrong. My guess is it's going to be related to something happening within Google's api.js and how it works with the grecaptcha variable. It's possible right after the ready function is triggered, their code does something with it, but I honestly can't say for certain. Commented Dec 13, 2021 at 20:46
  • Well, they mention to prevent race conditions on that page, so my best guess is that it could be that the global variable grecaptcha gets re-initialized at a certain point of time. So this must be done on purpose to make it rock solid and therefore not a mistake. Commented Dec 13, 2021 at 20:59

2 Answers 2

2

I think the code is wrong: https://jsfiddle.net/657b1gso/

Reported: https://github.com/google/recaptcha/issues/485

and works with a few small modifications: https://jsfiddle.net/p6ubwLme/

<script async src="https://www.google.com/recaptcha/api.js"></script>
<script>
  // How this code snippet works:
  // This logic overwrites the default behavior of `grecaptcha.ready()` to
  // ensure that it can be safely called at any time. When `grecaptcha.ready()`
  // is called before reCAPTCHA is loaded, the callback function that is passed
  // by `grecaptcha.ready()` is enqueued for execution after reCAPTCHA is
  // loaded.
  if(typeof grecaptcha === 'undefined') {
    grecaptcha = {};
  }
  grecaptcha.asyncready = function(cb){
    if(typeof grecaptcha.render === 'undefined') {
      // window.__grecaptcha_cfg is a global variable that stores reCAPTCHA's
      // configuration. By default, any functions listed in its 'fns' property
      // are automatically executed when reCAPTCHA loads.
      const c = '___grecaptcha_cfg';
      window[c] = window[c] || {};
      (window[c]['fns'] = window[c]['fns']||[]).push(cb);
    } else {
      cb();
    }
  }

  // Usage
  grecaptcha.asyncready(function(){
    grecaptcha.render("container", {
      sitekey: "ABC-123"
    });
  });
</script>
<div id="container">

</div>
Sign up to request clarification or add additional context in comments.

Comments

1

It'd have to be code elsewhere, but sure, it's a global object, so a malicious actor could potentially overwrite it, e.g. you could have something like:

var grecaptcha = { 
     start() {
         var that = this;
         return new Promise((resolve, reject) => {
             setTimeout(() => {
                 if (that.ready) {
                     that.ready(that)
                 }
                 resolve(true);
             }, 5000);
             let remaining = 4;
             let interval = setInterval(() => {
                 console.log(remaining--);
                 if (remaining == 0) {
                     clearInterval(interval);
                 }
             }, 1000);
         });
     }
 
};

grecaptcha.start().then(() => console.log('But we still do stuff with it'));

if(typeof grecaptcha === 'undefined') {
    grecaptcha = {};
}

grecaptcha.ready = function(cb){
    console.log(typeof grecaptcha);
}

setTimeout(() => grecaptcha = undefined, 1000)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.