Firebase电话认证报错:无法读取appVerificationDisabledForTesting属性
Firebase 10.6.0 React短信OTP重置密码时RecaptchaVerifier报错:无法读取
appVerificationDisabledForTesting属性 使用"firebase": "^10.6.0"在React项目中实现短信OTP重置密码功能时,初始化RecaptchaVerifier环节触发TypeError:无法读取undefined的appVerificationDisabledForTesting属性。按Firebase文档该属性应可访问,但实际运行报错。
实现代码
import React, { useState } from "react"; import Layout from "./../../components/Layout/Layout"; import toast from "react-hot-toast"; import OtpInput from "otp-input-react"; import PhoneInput from "react-phone-input-2"; import "react-phone-input-2/lib/style.css"; import { RecaptchaVerifier, signInWithPhoneNumber } from "firebase/auth"; import { Auth } from "../../firebase-config"; const ForgotPassword = () => { const [phone, setPhone] = useState(""); const [otp, setOtp] = useState(""); const [showOtpInterface, setShowOtpInterface] = useState(false); const onCaptchVerify = () => { try { // Create a new RecaptchaVerifier if (!window.recaptchaVerifier) { window.recaptchaVerifier = new RecaptchaVerifier( "recaptcha-container", { size: "invisible", callback: (response) => { onSignUp(); }, "expired-callback": () => { // Handle expiration if needed }, }, Auth ); } } catch (error) { console.log(error); } }; const onSignUp = () => { onCaptchVerify(); const appVerifier = window.recaptchaVerifier; const formatStr = `+${phone}`; signInWithPhoneNumber(Auth, formatStr, appVerifier) .then((confirmationResult) => { window.confirmationResult = confirmationResult; setShowOtpInterface(true); toast.success("OTP sent successfully"); }) .catch((error) => { toast.error("OTP sent failed"); console.log(error); }); }; const onOtpVerify = () => { window.confirmationResult .confirm(otp) .then(async (res) => { console.log(res); }) .catch((e) => { console.log(e); }); }; return ( <Layout title={"Forgot Password - Ecommerce APP"}> <div className="form-container"> <div> <h4 className="title">RESET PASSWORD</h4> <div id="recaptcha-container"></div> {showOtpInterface ? ( <> <label htmlFor="ph">Enter Your OTP</label> <OtpInput OTPLength={6} otpType="number" disabled={false} autoFocus value={otp} onChange={setOtp} /> <button type="submit" className="btn btn-primary mt-3" onClick={onOtpVerify} > Verify </button> </> ) : ( <> <label htmlFor="email">Enter your registered phone number</label> <PhoneInput country={"in"} value={phone} onChange={setPhone} /> <button type="submit" onClick={onSignUp} className="btn btn-primary mt-3" > Continue </button> </> )} </div> </div> </Layout> ); }; export default ForgotPassword;
错误堆栈信息
at _verifyPhoneNumber (phone.ts:186:1) at signInWithPhoneNumber (phone.ts:108:1) at onSignUp (ForgotPasssword.js:43:1) at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1) at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1) at invokeGuardedCallback (react-dom.development.js:4277:1) at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4291:1) at executeDispatch (react-dom.development.js:9041:1) at processDispatchQueueItemsInOrder (react-dom.development.js:9073:1) at processDispatchQueue (react-dom.development.js:9086:1) at dispatchEventsForPlugins (react-dom.development.js:9097:1) at react-dom.development.js:9288:1 at batchedUpdates$1 (react-dom.development.js:26140:1) at batchedUpdates (react-dom.development.js:3991:1) at dispatchEventForPluginEventSystem (react-dom.development.js:9287:1) at dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay (react-dom.development.js:6465:1) at dispatchEvent (react-dom.development.js:6457:1) at dispatchDiscreteEvent (react-dom.development.js:6430:1) TypeError: Cannot read properties of undefined (reading 'appVerificationDisabledForTesting') at new RecaptchaVerifier (recaptcha_verifier.ts:114:1) at onCaptchVerify (ForgotPasssword.js:19:1) at onSignUp (ForgotPasssword.js:39:1) at HTMLUnknownElement.callCallback (react-dom.development.js:4164:1) at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:1) at invokeGuardedCallback (react-dom.development.js:4277:1) at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4291:1) at executeDispatch (react-dom.development.js:9041:1) at processDispatchQueueItemsInOrder (react-dom.development.js:9073:1) at processDispatchQueue (react-dom.development.js:9086:1) at dispatchEventsForPlugins (react-dom.development.js:9097:1) at react-dom.development.js:9288:1 at batchedUpdates$1 (react-dom.development.js:26140:1) at batchedUpdates (react-dom.development.js:3991:1) at dispatchEventForPluginEventSystem (react-dom.development.js:9287:1) at dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay (react-dom.development.js:6465:1) at dispatchEvent (react-dom.development.js:6457:1) at dispatchDiscreteEvent (react-dom.development.js:6430:1)
排查思路与解决方案
1. 验证Auth实例有效性
检查../../firebase-config中导出的Auth是否通过getAuth(app)正确初始化:
// firebase-config.js示例代码 import { initializeApp } from "firebase/app"; import { getAuth } from "firebase/auth"; const firebaseConfig = { // 你的Firebase配置 }; const app = initializeApp(firebaseConfig); const Auth = getAuth(app); // 确保这一步正确执行 export { Auth };
若Auth为未初始化的对象或undefined,会导致RecaptchaVerifier初始化失败。
2. 修复RecaptchaVerifier初始化逻辑
当前代码中onSignUp调用onCaptchVerify,且onCaptchVerify的callback又调用onSignUp,形成无限循环,同时导致RecaptchaVerifier初始化时机异常。建议将初始化移至组件挂载阶段:
// 在组件中添加useEffect useEffect(() => { if (!window.recaptchaVerifier) { window.recaptchaVerifier = new RecaptchaVerifier( "recaptcha-container", { size: "invisible" }, // 移除循环调用的callback Auth ); } }, []);
3. 确保Recaptcha脚本加载
Firebase Auth会自动加载Recaptcha脚本,若存在加载延迟或网络问题,可手动在public/index.html中添加:
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
4. 检查Firebase项目配置
- 登录Firebase控制台,确保电话认证已启用;
- 在「认证」→「设置」→「授权域名」中,添加当前项目的域名(本地开发需添加
localhost)。
5. 修改后的完整代码
import React, { useState, useEffect } from "react"; import Layout from "./../../components/Layout/Layout"; import toast from "react-hot-toast"; import OtpInput from "otp-input-react"; import PhoneInput from "react-phone-input-2"; import "react-phone-input-2/lib/style.css"; import { RecaptchaVerifier, signInWithPhoneNumber } from "firebase/auth"; import { Auth } from "../../firebase-config"; const ForgotPassword = () => { const [phone, setPhone] = useState(""); const [otp, setOtp] = useState(""); const [showOtpInterface, setShowOtpInterface] = useState(false); useEffect(() => { // 组件挂载时初始化RecaptchaVerifier if (!window.recaptchaVerifier) { window.recaptchaVerifier = new RecaptchaVerifier( "recaptcha-container", { size: "invisible" }, Auth ); } }, []); const onSignUp = () => { const appVerifier = window.recaptchaVerifier; const formatStr = `+${phone}`; signInWithPhoneNumber(Auth, formatStr, appVerifier) .then((confirmationResult) => { window.confirmationResult = confirmationResult; setShowOtpInterface(true); toast.success("OTP sent successfully"); }) .catch((error) => { toast.error("OTP sent failed"); console.log(error); // 重置Recaptcha避免验证过期 window.recaptchaVerifier.render().then((widgetId) => { grecaptcha.reset(widgetId); }); }); }; const onOtpVerify = () => { window.confirmationResult .confirm(otp) .then(async (res) => { console.log(res); // 此处添加重置密码逻辑,例如: // await updatePassword(res.user, newPassword); // toast.success("Password reset successfully"); }) .catch((e) => { console.log(e); toast.error("OTP verification failed"); }); }; return ( <Layout title={"Forgot Password - Ecommerce APP"}> <div className="form-container"> <div> <h4 className="title">RESET PASSWORD</h4> <div id="recaptcha-container"></div> {showOtpInterface ? ( <> <label htmlFor="ph">Enter Your OTP</label> <OtpInput OTPLength={6} otpType="number" disabled={false} autoFocus value={otp} onChange={setOtp} /> <button type="button" className="btn btn-primary mt-3" onClick={onOtpVerify} > Verify </button> </> ) : ( <> <label htmlFor="phone">Enter your registered phone number</label> <PhoneInput country={"in"} value={phone} onChange={setPhone} /> <button type="button" onClick={onSignUp} className="btn btn-primary mt-3" > Continue </button> </> )} </div> </div> </Layout> ); }; export default ForgotPassword;
内容的提问来源于stack exchange,提问作者Naveen
相关产品推荐
相关产品推荐

