You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 11:17:04