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

Expo AuthSession Google登录首次调用报错 二次点击正常

Expo AuthSession 接入Google登录问题修复

问题现象

  • 首次打开应用调用Google登录函数直接报错,第二次点击调用时登录功能可正常运行
  • 抛出报错信息:

Cannot prompt to authenticate until the request has finished loading.(无法在身份验证请求加载完成前弹出授权提示)

  • 用户完成登录后刷新应用,无法默认获取到已登录的用户信息,Firebase登录态丢失

原实现代码如下:

import React, { useEffect, useState, createContext, useContext, useMemo } from "react"
import * as Google from "expo-auth-session/providers/google"
import * as WebBrowser from "expo-web-browser"
import { GoogleAuthProvider, signInWithCredential, auth, onAuthStateChanged, signOut } from "../firebase"

WebBrowser.maybeCompleteAuthSession()

const config = {
  expoClientId: xxxx
  webClientId: xxxx
  iosClientId: xxxx
  androidClientId: xxxx
}

const AuthContext = createContext({});

export const AuthProvider = ({children}) => {
  const [user,setUser] = useState(null)
  const [error,setError] = useState(null)
  const [req, res, login] = Google.useAuthRequest(config)

  const [loading, setLoading] = useState(true)
  useEffect(
    () =>
      onAuthStateChanged(auth, user => {
        setLoading(true)
        if (user) {
          setUser(user)
          setLoading(false)
        } else {
          setUser(null)
        }
        setLoading(false)
      }),
    []
  )
  const signInWithGoogle = () => {
    setLoading(true)
    login({ showInRecents: true })
      .then(async response => {
        if (response && response.type === "success") {
          const { accessToken, idToken } = response.authentication
          const credential = GoogleAuthProvider.credential(idToken, accessToken)
          await signInWithCredential(auth, credential)
          setLoading(false)
        }
      })
      .catch(error => console.error(error))
      .finally(() => setLoading(false))
  }

问题根因

  1. 首次点击报错:Google.useAuthRequest 钩子初始化是异步过程,组件首次挂载阶段授权请求还未完成加载,此时直接调用login()方法就会触发对应报错。等第一次点击报错后,授权请求已经在后台完成加载,所以第二次点击可以正常运行。另外原代码仅依赖login()返回的Promise处理响应,不符合expo-auth-session官方推荐的响应监听逻辑,容易出现状态同步问题。
  2. 刷新丢失登录态:Expo/React Native 环境下Firebase Auth默认使用内存级临时存储保存登录态,应用刷新/重启后内存数据清空,自然读不到之前的登录信息,没有配置适配RN环境的本地持久化方案。
  3. 细节语法问题:原代码中config对象的多个属性之间缺少逗号分隔,严格模式下会直接抛出语法错误;监听登录态的useEffect没有返回清理函数,存在内存泄漏风险。

修复方案

步骤1:修复首次点击报错问题

  • 调用login()方法前增加判断,只有当授权请求对象req不为空(代表初始化完成)时才触发登录流程
  • 用useEffect监听useAuthRequest返回的res响应对象,统一处理登录成功/失败/取消逻辑,替代直接在login()Promise里写业务逻辑的写法
  • 补全配置对象的语法分隔符,给登录态监听事件增加卸载清理逻辑

步骤2:修复Firebase登录态持久化问题

  • 安装依赖:npx expo install @react-native-async-storage/async-storage
  • 初始化Firebase Auth时,显式配置持久化方案为AsyncStorage本地存储,保证应用重启后登录态不丢失

修复后完整代码

import React, { useEffect, useState, createContext, useContext } from "react"
import * as Google from "expo-auth-session/providers/google"
import * as WebBrowser from "expo-web-browser"
import AsyncStorage from "@react-native-async-storage/async-storage"
import { 
  GoogleAuthProvider, 
  signInWithCredential, 
  initializeAuth, 
  getReactNativePersistence,
  onAuthStateChanged, 
  signOut 
} from "firebase/auth"
// 注意:替换你原来../firebase里的auth初始化代码,参考如下
// import { initializeApp } from "firebase/app"
// const firebaseConfig = {/* 你的firebase配置 */}
// const app = initializeApp(firebaseConfig)
// const auth = initializeAuth(app, {
//   persistence: getReactNativePersistence(AsyncStorage)
// })

WebBrowser.maybeCompleteAuthSession()

const config = {
  expoClientId: 'xxxx',
  webClientId: 'xxxx',
  iosClientId: 'xxxx',
  androidClientId: 'xxxx'
}

const AuthContext = createContext({});

export const AuthProvider = ({children}) => {
  const [user,setUser] = useState(null)
  const [error,setError] = useState(null)
  const [loading, setLoading] = useState(true)
  const [req, res, login] = Google.useAuthRequest(config)

  // 监听Google授权响应
  useEffect(() => {
    if (res?.type === "success") {
      const { accessToken, idToken } = res.authentication
      const credential = GoogleAuthProvider.credential(idToken, accessToken)
      signInWithCredential(auth, credential)
        .catch(err => setError(err))
        .finally(() => setLoading(false))
    }
    // 处理用户取消/关闭授权页的情况
    if (res?.type === "cancel" || res?.type === "dismiss") {
      setLoading(false)
    }
  }, [res])

  // 监听Firebase全局登录态
  useEffect(() => {
    const unsub = onAuthStateChanged(auth, user => {
      setLoading(true)
      if (user) {
        setUser(user)
      } else {
        setUser(null)
      }
      setLoading(false)
    })
    // 组件卸载时清理监听
    return () => unsub()
  }, [])

  const signInWithGoogle = async () => {
    setLoading(true)
    setError(null)
    // 核心校验:请求未初始化完成时拦截调用
    if (!req) {
      setLoading(false)
      setError(new Error("登录模块初始化中,请稍后重试"))
      return
    }
    try {
      await login({ showInRecents: true })
    } catch (err) {
      setError(err)
      setLoading(false)
    }
  }

  const value = {
    user,
    loading,
    error,
    signInWithGoogle,
    signOut: () => signOut(auth)
  }

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  )
}

export const useAuth = () => useContext(AuthContext)

额外注意点

  • 开发环境下建议暂时关闭React Strict Mode,严格模式下的双执行机制会导致auth钩子重复初始化,偶发触发同类报错
  • 确保Google云控制台里配置的OAuth客户端ID和config中填写的完全匹配,安卓端要正确配置包名和调试/发布版SHA签名

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 07:39:19