firebase / firebase-js-sdk

Firebase Javascript SDK
https://firebase.google.com/docs/web/setup
Other
4.83k stars 891 forks source link

RecaptchaVerifier error when used together with AppCheck #6133

Closed faizanabidnaqvi closed 2 years ago

faizanabidnaqvi commented 2 years ago

[REQUIRED] Describe your environment

[REQUIRED] Describe the problem

When I use the RecaptchaVerifier on my site with AppCheck enabled, I get an error when I call the verify method on the RecaptchaVerifier. The error is as follows: TypeError: this.getAssertedRecaptcha(...).render is not a function This only happens when AppCheck is enabled; without AppCheck it behaves normally, that is, the verification goes through. Also, it doesn't happen in development or production when running at localhost with the emulator. Only occurs in the deployed code in firebase hosting.

Screenshot 2022-04-10 at 6 29 34 PM

Steps to reproduce:

  1. Create a react app with firebase app check enabled
  2. Create a simple page with an invisible RecaptchaVerifier and a button that when clicked calls 'RecaptchaVerifier.verify()'
  3. Build the app and deploy to hosting
  4. Notice the error occurs when you press the button that calls the verify function

    Relevant Code:

In my root App.tsx file, appcheck is initialized this way

if(!getApps().length){
  const app = initializeApp(firebaseConfig)
  // console.log(`Initializing app check with ${import.meta.env.VITE_RECAPTCHA_KEY}`)
  //if localhost set debug app check token
  //https://firebase.google.com/docs/app-check/web/debug-provider?authuser=0&hl=en
  if (window.location.hostname === "localhost"){
    //this must be set to true to generate a new token and once generated and registered, we need to set the token value in env
    //(self as any).FIREBASE_APPCHECK_DEBUG_TOKEN = true
    (window.self as any).FIREBASE_APPCHECK_DEBUG_TOKEN = import.meta.env.VITE_APP_CHECK_DEBUG_TOKEN
  }

  initializeAppCheck(app, {
    provider: new ReCaptchaEnterpriseProvider(import.meta.env.VITE_RECAPTCHA_KEY),
    isTokenAutoRefreshEnabled: true
  })
}

The RecaptchaVerifier component is as follows

export const FirebaseRecaptcha = ({instructionMessage, onVerificationComplete}: FirebaseRecaptchaProps)=>{

    const [isCaptchaLoading, setCaptchaLoading] = useState(false)
    const [verificationErrored, setVerificationErrored]= useState(false)

    let verifier: any = null
    useEffect(()=>{
      console.log("Initializing captcha");

      verifier = new RecaptchaVerifier("recaptcha-container", {
        'size': 'invisible',
        'callback': function(response: any) {
          console.log("verification success")
          setCaptchaLoading(false)
          onVerificationComplete(CaptchaVerificationStatus.SUCCESS)
        }
      }, getAuth())
    },[])

    const onStartClick = async ()=> {
      if(verifier){
        try {
          setCaptchaLoading(true)
          console.log("About to verify")
          const res = await verifier.verify()
        }catch(e){
          setCaptchaLoading(false)
          console.error(e)
          onVerificationComplete(CaptchaVerificationStatus.FAIL)
        }
      }
    }

    // <Button variant="contained" color="primary" id={recaptchaButtonId} onClick={()=>onStartClick()} className={`${classes.actionButton} ${classes.bottomMargin}`}>
    //   Start
    // </Button>

    return (
        <>
          <div id="recaptcha-container"></div>
          <Typography variant="h6" gutterBottom sx={{mb: 2}}>
            {instructionMessage ? instructionMessage : getStrings().message_start_page}
          </Typography>
          {isCaptchaLoading ?
            <CircularProgress sx={{mb: 2}} /> :
            <Button variant="contained" color="primary" onClick={()=>onStartClick()} sx={{mb: 2, width: '50%'}}>
              {getStrings().button_start}
            </Button>
          }
        </>
      )
  }
jbalidiong commented 2 years ago

Hi @faizanabidnaqvi, thanks for the report. I was able to reproduce the behavior now. Let me check what we can do for this issue or bring someone here that can provide more context about it. I’ll update this thread if I have any information to share.

votenex commented 2 years ago

I am currently experiencing this. Phone signin worked before app check. After app check, getting error "this.getAssertedRecaptcha(...).render is not a function".

I unenforced app check for now since I need my website to require my users to register both email and phone. Hoping for a prompt resolution on this.

votenex commented 2 years ago

Is there an update on this?

VFertak commented 2 years ago

any update on this issue?

stjoe4 commented 2 years ago

any update?

stjoe4 commented 2 years ago

It works with ReCaptchaV3Provider

votenex commented 2 years ago

It works with ReCaptchaV3Provider

I used app check ReCaptchaV3Provider with phone signin. I had the same issue. So it worked for you?

stjoe4 commented 2 years ago

Yes it works with ReCaptchaV3Provider, but have the same error with ReCaptchaEnterpriseProvider.

weixifan commented 2 years ago

Thank you for reporting this issue. We're looking into it. Internal bug filed: b/237289338.

hsubox76 commented 2 years ago

I actually haven't been able to reproduce this by using the code in the first post and trying to fill in the blanks. I've tried localhost dev server, localhost prod build, and deployed Firebase hosting, and they all work. Does anyone have a minimal repro I can use? (preferably one that errors in localhost, but if not, just let me know what environment)

votenex commented 2 years ago

When I had this error, I tried both v3 and enterprise. It doesn't even get to the verify code part.

app check:

const appCheck = initializeAppCheck(firebaseApp, {
    provider: new ReCaptchaV3Provider('***********************'),
    isTokenAutoRefreshEnabled: true
});

phone auth:

setPersistence(auth, browserLocalPersistence)
        .then(() => {
            // Sign in
            signInWithPhoneNumber(auth, usermobile, recaptchaVerifier)
            .then((confirmationResult) => {
                $('#otpcode').show();
                $('#phone-signin-sendcode').hide();
                $('#phone-signin-verifycode').show();

                $('#phone-signin-verifycode').click(async function() {
                    const code = document.getElementById('otpcode').value;
                    window.confirmationResult = confirmationResult;

                    let x = {
                        mobile: usermobile,
                        userid: z,
                        code: code
                    }
                    verifyCode(x)
                })

            }).catch((error) => {
                alert('An error occurred! ' + error.code + " " + error.message);
                window.recaptchaVerifier.render().then(function(widgetId) {
                    grecaptcha.reset(widgetId);
                });
            });
        })
        .catch((error) => {
            alert('An error occurred! ' + error.code + " " + error.message);
        });

even if I used self.FIREBASE_APPCHECK_DEBUG_TOKEN = true; It still didn't work.

a few weeks ago, v3 started working for me. But enterprise still doesn't work.

hsubox76 commented 2 years ago

I'm able to reproduce with App Check's ReCaptchaEnterpriseProvider only, and I think I can get a quick fix done for that. If you're experiencing this error with ReCaptchaV3Provider, it would be great if you can provide a reliable minimal repro of it with all the extraneous parts stripped out. Will post a PR for the Enterprise fix soon.

hsubox76 commented 2 years ago

Let me keep this open because there's still some uncertainty about if there is a bug with RecaptchaV3.

ehedaya commented 2 years ago

I have this error and it started when I switched from using regular recaptcha to enterprise recaptcha on my firebase project. Seems like there's a difference between the object that gets returned by enterprise vs normal recaptcha, this is the object with the missing method in debug mode

Screen Shot 2022-08-01 at 2 39 12 PM

it seems like it may be a result of both libraries (enterprise and non-, which is i assume what the auth library uses) loading on the same page

hsubox76 commented 2 years ago

This should be fixed in 9.9.1, if it's not the case, can you try a fresh reinstall to make sure nothing was left behind? (remove node_modules and yarn.lock and reinstall). If that doesn't work, can you provide a minimal repro? Similar comment from https://github.com/firebase/firebase-js-sdk/issues/6485#issuecomment-1201612743:

I'm not able to reproduce this, are you sure you are using the latest version? It should have been fixed by https://github.com/firebase/firebase-js-sdk/issues/6133. Is it possible something got left over when upgrading from an older version? Can you clean out your node_modules and yarn.lock and try to reinstall firebase? I tried to create a minimal reproduction using this code:

const appCheck = initializeAppCheck(app, { provider: new ReCaptchaEnterpriseProvider('MY_SITE_KEY')});

const verifier = new RecaptchaVerifier('container', { size: 'invisible' }, getAuth(app));

const provider = new PhoneAuthProvider(getAuth(app));
await provider.verifyPhoneNumber('+1-510-555-5555', verifier);

And everything seemed to work. If a fresh install doesn't help, how and when are you initializing App Check? I didn't see it in the sample code.

It sounds like there may still be an issue but I'm unable to reproduce it on my end. I've tried swapping the order of App Check and RecaptchaVerifier and it doesn't seem to break it either.

ehedaya commented 2 years ago

Thanks! That did fix it!

hsubox76 commented 2 years ago

I haven't heard back from the other person who reported it in https://github.com/firebase/firebase-js-sdk/issues/6485 so I'll close this again, can reopen if someone reports the problem again.