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

使用expo-auth-session实现Google登录遇双重问题求助

Expo Google登录问题排查与解决

问题概述

  1. 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

  1. 已构建的安卓应用登录成功后无法跳转回应用,停留在弹窗内的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登录错误

  1. 检查Google Cloud Console的Expo客户端配置
    打开Google Cloud Console,找到你用的expoClientId对应的OAuth客户端条目,确认授权重定向URI里加了https://auth.expo.io/@leonhick/CafeRewards——这个要和你代码里的projectNameForProxy完全对应,差一个字符都不行。

  2. 强制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了。

  3. 确认redirectUri是对的
    打印一下redirectUri看看,Expo环境下应该是https://auth.expo.io/...开头的代理地址,不是本地的exp://——如果还是本地地址,说明REDIRECT_PARAMS的判断逻辑有问题,检查Constants.appOwnership的值是不是真的等于"expo"。

二、修复安卓打包应用的跳转和登录保存问题

  1. 配置安卓客户端的重定向URI
    在Google Cloud Console里,安卓客户端ID的授权重定向URI必须设为com.leonhick.caferewards://oauthredirect,格式就是你的scheme://oauthredirect,scheme要和app.json里的com.leonhick.caferewards完全一致。

  2. 确保WebBrowser.maybeCompleteAuthSession()生效
    这个方法必须放在组件最顶部,不能被任何条件判断包着,确保登录成功后能自动关闭弹窗并回到应用。你代码里已经写了,但再检查下有没有被注释或者条件限制。

  3. 排查登录成功后的逻辑

    • 登录成功后,先确认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是不是真的把状态更新了。
  4. 核对app.json的scheme配置
    确保scheme字段com.leonhick.caferewards和安卓包名com.leonhick.caferewards完全一样,Google那边的重定向URI也要用这个scheme。

内容的提问来源于stack exchange,提问作者L.Hickingbotham

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 20:47:33