Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

detect when challenge window is closed for Google recaptcha

I am using Google invisible recaptcha. Is there a way to detect when the challenge window is closed? By challenge window I mean window where you have to pick some images for verification.

I currently put a spinner on the button that rendered the recaptcha challenge, once the button is clicked. There is no way for the user to be prompted with another challenge window.

I am calling render function programmatically:

grecaptcha.render(htmlElement, { callback: this.verified, expiredCallback: this.resetRecaptcha, sitekey: this.siteKey, theme: "light", size: "invisible" });

I have 2 callback functions wired up the verified and the resetRecaptcha functions which look like:

function resetRecaptcha() {
        grecaptcha.reset();
    }

function verified(recaptchaResponse)
{
/*
which calls the server to validate
*/
}

I would have expected that grecaptcha.render has another callback that is called when the challenge screen is closed without the user verifying himself by selecting the images.

like image 289
Tarek Avatar asked Apr 19 '17 06:04

Tarek


People also ask

How long does reCAPTCHA block last?

Note: reCAPTCHA tokens expire after two minutes. If you're protecting an action with reCAPTCHA, make sure to call execute when the user takes the action rather than on page load. You can execute reCAPTCHA on as many actions as you want on the same page.

How do I know if invisible reCAPTCHA is working?

You can test invisible recaptcha by using Chrome emulator. You will need to add a new custom device (BOT) in developer tools, and set User Agent String to Googlebot/2.1 on Desktop . Then use the new BOT device when testing on your site to trigger the recaptcha authentication.


2 Answers

As you mention, the API doesn't support this feature.

However, you can add this feature yourself. You may use the following code with caution, Google might change its reCaptcha and by doing so break this custom code. The solution relies on two characteristics of reCaptcha, so if the code doesn't work, look there first:

  • the window iframe src: contains "google.com/recaptcha/api2/bframe"
  • the CSS opacity property: changed to 0 when the window is closed

 

// to begin: we listen to the click on our submit button
// where the invisible reCaptcha has been attachtted to
// when clicked the first time, we setup the close listener
recaptchaButton.addEventListener('click', function(){
    if(!window.recaptchaCloseListener) initListener()

})

function initListener() {

    // set a global to tell that we are listening
    window.recaptchaCloseListener = true

    // find the open reCaptcha window
    HTMLCollection.prototype.find = Array.prototype.find
    var recaptchaWindow = document
        .getElementsByTagName('iframe')
        .find(x=>x.src.includes('google.com/recaptcha/api2/bframe'))
        .parentNode.parentNode

    // and now we are listening on CSS changes on it
    // when the opacity has been changed to 0 we know that
    // the window has been closed
    new MutationObserver(x => recaptchaWindow.style.opacity == 0 && onClose())
        .observe(recaptchaWindow, { attributes: true, attributeFilter: ['style'] })

}

// now do something with this information
function onClose() {
    console.log('recaptcha window has been closed')
}
like image 102
arc Avatar answered Sep 20 '22 05:09

arc


The drawback of detecting when iframe was hidden is that it fires not only when user closes captcha by clicking in background, but also when he submits the answer.

What I needed is detect only the first situation (cancel captcha).

I created a dom observer to detect when captcha is attached to DOM, then I disconnect it (because it is no longer needed) and add click handler to its background element.

Keep in mind that this solution is sensitive to any changes in DOM structure, so if google decides to change it for whatever reason, it may break.

Also remember to cleanup the observers/listeners, in my case (react) I do it in cleanup function of useEffect.

    const captchaBackgroundClickHandler = () => {
        ...do whatever you need on captcha cancel
    };

    const domObserver = new MutationObserver(() => {
        const iframe = document.querySelector("iframe[src^=\"https://www.google.com/recaptcha\"][src*=\"bframe\"]");

        if (iframe) {
            domObserver.disconnect();

            captchaBackground = iframe.parentNode?.parentNode?.firstChild;
            captchaBackground?.addEventListener("click", captchaBackgroundClickHandler);
        }
    });

    domObserver.observe(document.documentElement || document.body, { childList: true, subtree: true });
like image 28
Krystyna Solawa Avatar answered Sep 20 '22 05:09

Krystyna Solawa