使用expo-auth-session谷歌登录后,Firebase未获取到凭证的问题
问题排查与修复方案
你的代码存在几个关键问题导致凭证无法同步到Firebase,以下是具体问题和修复步骤:
1. idToken赋值错误
你错误地将accessToken赋值给了idToken,Firebase的GoogleAuthProvider.credential()需要的是谷歌返回的idToken,而非accessToken。
错误代码:
const idToken = response.authentication.accessToken;
修复:
const idToken = response.authentication.idToken; // 正确获取idToken
2. 未处理异步登录操作的结果与错误
signInWithCredential是异步函数,你没有处理它的Promise结果,也未捕获错误,导致登录失败时无法排查问题,同时也没有更新本地用户状态。
修复:使用async/await处理异步操作,并更新user状态:
React.useEffect(() => { const handleLogin = async () => { if (response?.type === "success") { try { const { accessToken, idToken } = response.authentication; const credential = GoogleAuthProvider.credential(idToken, accessToken); const userCredential = await signInWithCredential(auth, credential); setUser(userCredential.user); // 更新本地用户状态 } catch (error) { console.error("Firebase登录失败:", error); // 打印错误便于排查 } } }; handleLogin(); }, [response]);
3. AuthContext中的user固定为null
你在Context Provider中硬编码了user: null,导致组件无法获取到实际登录后的用户信息。
修复:将本地state中的user传入Context:
<AuthContext.Provider value={{ user, // 使用本地state的user promptAsync, }}> {children} </AuthContext.Provider>
4. 未监听Firebase用户状态变化
应该通过onAuthStateChanged监听Firebase的用户状态,确保页面刷新或其他登录方式下,本地user状态能同步更新。
修复:添加监听逻辑:
React.useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (currentUser) => { setUser(currentUser); // 同步Firebase用户状态到本地 }); return unsubscribe; // 组件卸载时取消监听 }, []);
5. 无效的scopes配置
expo-auth-session/providers/google的配置中,permissions参数是多余的,且scopes需要包含openid才能获取到idToken(这是谷歌OAuth2获取idToken的必要条件)。
修复调整scopes:
const [request, response, promptAsync] = google.useAuthRequest({ androidClientId: '***', iosClientId: '***', clientId: '***', scopes: ["openid", "email", "profile"], // 正确的scopes,openid必须 });
修正后的完整useAuth.js代码
import { createContext, useContext } from "react"; import { View, StyleSheet } from "react-native"; import * as React from 'react'; import * as google from 'expo-auth-session/providers/google'; import * as WebBrowser from 'expo-web-browser'; import { GoogleAuthProvider, onAuthStateChanged, signInWithCredential } from "@firebase/auth"; import { auth } from "../firebase"; WebBrowser.maybeCompleteAuthSession(); const AuthContext = createContext({}); export const AuthProvider = ({ children }) => { const [user, setUser] = React.useState(null); const [request, response, promptAsync] = google.useAuthRequest({ androidClientId: '***', iosClientId: '***', clientId: '***', scopes: ["openid", "email", "profile"], }); // 监听Firebase用户状态变化 React.useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (currentUser) => { setUser(currentUser); }); return unsubscribe; }, []); // 处理谷歌登录回调 React.useEffect(() => { const handleLogin = async () => { if (response?.type === "success") { try { const { accessToken, idToken } = response.authentication; const credential = GoogleAuthProvider.credential(idToken, accessToken); await signInWithCredential(auth, credential); } catch (error) { console.error("Firebase登录失败:", error); } } }; handleLogin(); }, [response]); return ( <AuthContext.Provider value={{ user, promptAsync, }}> {children} </AuthContext.Provider> ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff', alignItems: 'center', justifyContent: 'center', }, }); export default function useAuth() { return useContext(AuthContext); }
额外优化:LoginScreen中的冗余代码
你的LoginScreen中存在冗余的解构赋值,可以简化:
import React from "react"; import { View, Text, Button } from "react-native"; import useAuth from "../hooks/useAuth"; const LoginScreen = () => { const { promptAsync, user } = useAuth(); console.log(user); return ( <View> <Text>Login to the app</Text> <Button title="Login" onPress={() => promptAsync()} /> </View> ) } export default LoginScreen
内容的提问来源于stack exchange,提问作者Abdiaziz Abdullahi
相关产品推荐
相关产品推荐

