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

React Native Expo集成Firebase手机号认证遇missing-client-identifier错误求助

问题描述

我在React Native Expo应用中使用react-native-firebase库实现手机号认证时遇到问题,用户名密码认证功能正常,但手机号认证报错:

[Error: [auth/missing-client-identifier] This request is missing a valid app identifier, meaning that neither SafetyNet checks nor reCAPTCHA checks succeeded. Please try again, or check the logcat for more details.]

我尝试设置auth().settings.appVerificationDisabledForTesting = true但无效,希望获得基于现有依赖(react-native-firebase、Expo)的跨Android/iOS的reCAPTCHA手机号认证可运行示例。

原代码如下:

import {
  Text, View, KeyboardAvoidingView, TouchableOpacity,
} from "react-native";
import React, { useEffect } from "react";
import { useNavigation } from "@react-navigation/native";
import auth from "@react-native-firebase/auth";
import styles from "../../styles/base";

function RegisterScreen() {
  const navigation = useNavigation();

  useEffect(() => {
    const unsubscribe = auth().onAuthStateChanged((user) => {
      if (user) {
        navigation.replace("Authenticated");
      }
    });
    return unsubscribe;
  }, []);

  const handleSignUp = () => {
    console.log(auth().settings.appVerificationDisabledForTesting);
    auth().signInWithPhoneNumber("*************").then(() => {
      console.log("successfull");
    }).catch((error) => {
      console.log(error);
      console.log("error");
    });
  };

  return (
    <KeyboardAvoidingView
      style={styles.container}
      behavior="padding"
    >
      <View style={styles.buttonContainer}>
        <TouchableOpacity
          onPress={handleSignUp}
          style={styles.button}
        >
          <Text style={styles.buttonText}>Register</Text>
        </TouchableOpacity>
      </View>
      <View>
        <Text style={styles.accountText} onPress={() => navigation.replace("Login")}>I already have an account.</Text>
      </View>

    </KeyboardAvoidingView>
  );
}

export default RegisterScreen;
解决方案

核心说明

Expo环境下使用react-native-firebase做手机号认证,必须集成Firebase的reCAPTCHA验证(SafetyNet仅支持Android且需额外配置)。以下是完整可运行的实现步骤和代码:

步骤1:前置配置检查

  • 确认Expo项目已正确链接react-native-firebase,google-services.json(Android)和GoogleService-Info.plist(iOS)已放置在对应目录。
  • 在Firebase控制台的Authentication > 登录方法中启用手机号登录选项。

步骤2:完整可运行代码示例

import {
  Text, View, KeyboardAvoidingView, TouchableOpacity, TextInput, Alert,
} from "react-native";
import React, { useEffect, useRef, useState } from "react";
import { useNavigation } from "@react-navigation/native";
import auth from "@react-native-firebase/auth";
import styles from "../../styles/base";

function RegisterScreen() {
  const navigation = useNavigation();
  const recaptchaVerifier = useRef(null);
  const [phoneNumber, setPhoneNumber] = useState("+8613xxxxxxxxx"); // 替换为测试或用户输入号码
  const [verificationId, setVerificationId] = useState("");
  const [verificationCode, setVerificationCode] = useState("");

  useEffect(() => {
    // 初始化reCAPTCHA验证器
    recaptchaVerifier.current = new auth.RecaptchaVerifier("recaptcha-container", {
      size: "invisible", // 隐藏式验证,也可设置为"normal"显示组件
      callback: () => {
        // reCAPTCHA验证成功,无需额外操作
      },
      "expired-callback": () => {
        Alert.alert("reCAPTCHA已过期,请重新尝试");
      },
    }, auth());

    // 监听用户登录状态
    const unsubscribe = auth().onAuthStateChanged((user) => {
      if (user) {
        navigation.replace("Authenticated");
      }
    });
    return unsubscribe;
  }, []);

  // 请求短信验证码
  const requestVerificationCode = async () => {
    try {
      const confirmation = await auth().signInWithPhoneNumber(phoneNumber, recaptchaVerifier.current);
      setVerificationId(confirmation.verificationId);
      Alert.alert("验证码已发送至您的手机");
    } catch (error) {
      console.error("请求验证码失败:", error);
      Alert.alert("错误", error.message);
    }
  };

  // 验证验证码并完成登录
  const verifyCode = async () => {
    try {
      const credential = auth.PhoneAuthProvider.credential(verificationId, verificationCode);
      await auth().signInWithCredential(credential);
      Alert.alert("登录成功");
    } catch (error) {
      console.error("验证失败:", error);
      Alert.alert("错误", error.message);
    }
  };

  return (
    <KeyboardAvoidingView style={styles.container} behavior="padding">
      {/* 必须保留此容器,作为reCAPTCHA的渲染载体 */}
      <View id="recaptcha-container" style={{ height: 0, width: 0 }} />

      {!verificationId ? (
        <View style={styles.inputContainer}>
          <TextInput
            style={styles.input}
            placeholder="输入手机号(含国家码,如+8613xxxxxxxxx)"
            value={phoneNumber}
            onChangeText={setPhoneNumber}
            keyboardType="phone-pad"
          />
          <TouchableOpacity onPress={requestVerificationCode} style={styles.button}>
            <Text style={styles.buttonText}>获取验证码</Text>
          </TouchableOpacity>
        </View>
      ) : (
        <View style={styles.inputContainer}>
          <TextInput
            style={styles.input}
            placeholder="输入6位验证码"
            value={verificationCode}
            onChangeText={setVerificationCode}
            keyboardType="number-pad"
            maxLength={6}
          />
          <TouchableOpacity onPress={verifyCode} style={styles.button}>
            <Text style={styles.buttonText}>验证并登录</Text>
          </TouchableOpacity>
        </View>
      )}

      <View style={{ marginTop: 20 }}>
        <Text style={styles.accountText} onPress={() => navigation.replace("Login")}>我已有账号</Text>
      </View>
    </KeyboardAvoidingView>
  );
}

export default RegisterScreen;

关键注意事项

  • reCAPTCHA容器:必须保留带有id="recaptcha-container"的View,即使设置为隐藏,这是验证器的必要渲染载体。
  • 手机号格式:必须包含完整国家码(如+86),否则会触发验证失败。
  • 测试模式:若要本地测试,除设置auth().settings.appVerificationDisabledForTesting = true,还需使用Firebase控制台提供的测试手机号(在登录方法的手机号设置中添加),真实号码仍会触发reCAPTCHA验证。
  • iOS配置:确保Info.plist已添加网络权限,GoogleService-Info.plist配置正确。

内容的提问来源于stack exchange,提问作者Joel Suter

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 10:02:46