使用expo-auth-session实现Google登录遇双重问题求助
问题概述
- Expo Go应用中Google登录失败,提示:
"You can't sign in to this app because it doesn't comply with Google OAuth 2.0 policy for keeping apps secure."
错误代码400:invalid_request,请求详情:redirect_uri=exp://192.168.1.66:8081
- 已构建的安卓应用登录成功后无法跳转回应用,停留在弹窗内的Google主页,关闭弹窗后登录信息未保存。
已尝试设置useProxy选项并提供expoClientId,但Expo Go仍默认使用androidClientId。
期望解决目标
- 实现Expo Go应用内正常的Google登录
- 安卓打包应用登录后正确跳转回应用并保存登录信息
相关代码
App核心代码
import Home from "./Home"; import { useEffect, useState } from "react"; import * as WebBrowser from "expo-web-browser"; import * as Google from "expo-auth-session/providers/google"; import AsyncStorage from "@react-native-async-storage/async-storage"; import * as AuthSession from "expo-auth-session"; import { Button, View, ActivityIndicator, StyleSheet, Text, } from "react-native"; import { createMongoUser, getMongoUser } from "./HTTP"; import LoginScreen from "./Views/LoginScreen"; import config from "./HTTP/config.json"; import registerNNPushToken, { getPushDataInForeground } from "native-notify"; import Constants from "expo-constants"; WebBrowser.maybeCompleteAuthSession(); const EXPO_REDIRECT_PARAMS = { useProxy: true, projectNameForProxy: "@leonhick/CafeRewards", }; const NATIVE_REDIRECT_PARAMS = { native: "com.leonhick.caferewards://" }; const REDIRECT_PARAMS = Constants.appOwnership === "expo" ? EXPO_REDIRECT_PARAMS : NATIVE_REDIRECT_PARAMS; const redirectUri = AuthSession.makeRedirectUri(REDIRECT_PARAMS); let skipLoginUser = { _id: { $oid: "657b867e87316912d382fef0" }, email: "leonhickingbotham@gmail.com", verified_email: true, name: "Leon Hickingbotham", given_name: "Leon", family_name: "Hickingbotham", picture: "https://lh3.googleusercontent.com/a/ACg8ocLgcUmdvrgOtfzhihWHxlobB1FKWrtdCw2gjuuW678CgF8=s96-c", locale: "en", companies: [ { name: "Real test", id: "5164a144-61f6-4538-89e5-081fa8ad7424", rewards: "0.00", }, { name: "Test company real 2", id: "bebee9d9-ef59-48e1-8d6b-67bc061ef2de", rewards: "1.20", }, ], }; export default function () { // registerNNPushToken(config.appId, config.appToken); const [tempUser, setTempUser] = useState(null); const [userInfo, setUserInfo] = useState( // skipLoginUser null ); const [request, response, promptAsync] = Google.useAuthRequest( { androidClientId: "285363570582-vq6713tddf62uigs9stumoqmaul8rh2i.apps.googleusercontent.com", // iosClientId: // "285363570582-3beu4apl3e73adqm9f9vu30l8bh0gj9q.apps.googleusercontent.com", webClientId: // "285363570582-0v78j4ql35fe0j133uinods9iknjgius.apps.googleusercontent.com", expoClientId: "285363570582-lfn286l4sqo410hbsuhmos98cj8a9d8m.apps.googleusercontent.com", }, redirectUri ); const [isLoading, setIsLoading] = useState(true); const getUserInfo = async (token) => { if (!token) return; console.log("found token"); try { const response = await fetch( "https://www.googleapis.com/userinfo/v2/me", { headers: { Authorization: `Bearer ${token}` }, } ); console.log("hit googleapi"); const user = await response.json(); setTempUser(user); await AsyncStorage.setItem("@user", JSON.stringify(user)); console.log("storing user in cache"); await handleMongoDb(user); } catch (error) { // handle errors } }; async function wrapLoader() { // console.log(response); setIsLoading(true); await handleSignInWithGoogle(); setIsLoading(false); } useEffect(() => { wrapLoader(); }, [response]); async function handleMongoDb(user) { // handle mongoDb here console.log("hitting mongoDB"); let { id, ...usableUser } = user; console.log(user.email); let fullUser = await getMongoUser(user.email); if (!fullUser || (fullUser.error && fullUser.code === 404)) { // create new entry in MongoDb let newFullUser = await createMongoUser(usableUser); setUserInfo(newFullUser); } else { // save user locally setUserInfo(fullUser); } } async function handleSignInWithGoogle() { console.log("trying to auth"); const user = await AsyncStorage.getItem("@user"); if (!user) { if (response?.type === "success") { console.log("trying to get access token"); await getUserInfo(response.authentication.accessToken); } return; } else { await handleMongoDb(JSON.parse(user)); } } return userInfo ? ( <Home userInfo={userInfo} setUserInfo={setUserInfo} handleMongoDb={handleMongoDb} /> ) : isLoading ? ( <View style={styles.container}> <ActivityIndicator /> </View> ) : tempUser ? ( <View> <Text>{JSON.stringify(tempUser)}</Text> </View> ) : ( <LoginScreen promptAsync={promptAsync} /> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: "center", }, });
app.json配置
{ "expo": { "name": "Cafe Rewards", "slug": "CafeRewards", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "light", "splash": { "image": "./assets/splash.png", "resizeMode": "contain", "backgroundColor": "#ffffff" }, "assetBundlePatterns": ["**/*"], "ios": { "supportsTablet": true, "googleServicesFile": "./Info.plist", "bundleIdentifier": "com.leonhick.caferewards" }, "android": { "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#ffffff" }, "package": "com.leonhick.caferewards" }, "web": { "favicon": "./assets/favicon.png" }, "scheme": "com.leonhick.caferewards", "extra": { "eas": { "projectId": "0e18f137-da1b-4325-bc81-adaa72e62ca3" } } } }
解决方案
一、搞定Expo Go的Google登录错误
检查Google Cloud Console的Expo客户端配置
打开Google Cloud Console,找到你用的expoClientId对应的OAuth客户端条目,确认授权重定向URI里加了https://auth.expo.io/@leonhick/CafeRewards——这个要和你代码里的projectNameForProxy完全对应,差一个字符都不行。强制Expo Go只用expoClientId
别让Expo乱选clientId,直接根据环境动态传参:const [request, response, promptAsync] = Google.useAuthRequest( { ...(Constants.appOwnership === "expo" ? { expoClientId: "285363570582-lfn286l4sqo410hbsuhmos98cj8a9d8m.apps.googleusercontent.com" } : { androidClientId: "285363570582-vq6713tddf62uigs9stumoqmaul8rh2i.apps.googleusercontent.com" }), // webClientId如果需要的话再打开注释 }, redirectUri );这样Expo Go环境下就只会用expoClientId,不会碰androidClientId了。
确认redirectUri是对的
打印一下redirectUri看看,Expo环境下应该是https://auth.expo.io/...开头的代理地址,不是本地的exp://——如果还是本地地址,说明REDIRECT_PARAMS的判断逻辑有问题,检查Constants.appOwnership的值是不是真的等于"expo"。
二、修复安卓打包应用的跳转和登录保存问题
配置安卓客户端的重定向URI
在Google Cloud Console里,安卓客户端ID的授权重定向URI必须设为com.leonhick.caferewards://oauthredirect,格式就是你的scheme://oauthredirect,scheme要和app.json里的com.leonhick.caferewards完全一致。确保WebBrowser.maybeCompleteAuthSession()生效
这个方法必须放在组件最顶部,不能被任何条件判断包着,确保登录成功后能自动关闭弹窗并回到应用。你代码里已经写了,但再检查下有没有被注释或者条件限制。排查登录成功后的逻辑
- 登录成功后,先确认
response.authentication.accessToken有没有拿到,打个日志看看 - 检查
AsyncStorage.setItem("@user", ...)是不是真的成功了,加个try-catch捕获错误:try { await AsyncStorage.setItem("@user", JSON.stringify(user)); console.log("用户信息保存成功"); } catch (e) { console.error("保存用户失败:", e); } - 看看
handleMongoDb里有没有正确处理返回的用户数据,setUserInfo是不是真的把状态更新了。
- 登录成功后,先确认
核对app.json的scheme配置
确保scheme字段com.leonhick.caferewards和安卓包名com.leonhick.caferewards完全一样,Google那边的重定向URI也要用这个scheme。
内容的提问来源于stack exchange,提问作者L.Hickingbotham

