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
相关产品推荐
相关产品推荐

