React Native Expo Google登录授权失败:重定向URI配置问题求助
我正在为React Native(Expo)应用集成Google登录功能,用户可通过Google账号直接登录,无需注册新账号。尝试过多种方案后仍未解决,推测问题出在Google开发者控制台的授权重定向URI配置上。
实现细节
GoogleLogin组件代码
import React, { useState, useEffect } from "react"; import { Button, View, Text } from "react-native"; import * as Google from "expo-auth-session/providers/google"; import * as WebBrowser from "expo-web-browser"; import { GOOGLE_WEB_CLIENT_ID, GOOGLE_ANDROID_CLIENT_ID, GOOGLE_IOS_CLIENT_ID, } from "@env"; import * as Linking from "expo-linking"; console.log("GOOGLE_WEB_CLIENT_ID:", GOOGLE_WEB_CLIENT_ID); console.log("GOOGLE_ANDROID_CLIENT_ID:", GOOGLE_ANDROID_CLIENT_ID); console.log("GOOGLE_IOS_CLIENT_ID:", GOOGLE_IOS_CLIENT_ID); WebBrowser.maybeCompleteAuthSession(); const GoogleLogin = ({ onLoginSuccess }) => { const [request, response, promptAsync] = Google.useAuthRequest({ expoClientId: GOOGLE_WEB_CLIENT_ID, iosClientId: GOOGLE_IOS_CLIENT_ID, androidClientId: GOOGLE_ANDROID_CLIENT_ID, webClientId: GOOGLE_WEB_CLIENT_ID, redirectUri: Linking.createURL("/auth/callback"), }); const [userInfo, setUserInfo] = useState(null); useEffect(() => { console.log("Google Auth Request:", request); console.log("Google Auth Response:", response); if (response) { if (response.type === "success") { const { authentication } = response; console.log("Authentication successful:", authentication); if (authentication) { fetchUserInfo(authentication.accessToken); } } else if (response.type === "error") { console.error("Google Auth Error:", response.params); } } }, [response]); const fetchUserInfo = async (token) => { try { const response = await fetch( "https://www.googleapis.com/userinfo/v2/me", { headers: { Authorization: `Bearer ${token}` }, } ); if (!response.ok) { throw new Error("Failed to fetch user info"); } const user = await response.json(); console.log("Fetched user info:", user); setUserInfo(user); onLoginSuccess(user); } catch (error) { console.error("Error fetching user info:", error); } }; return ( <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}> <Button disabled={!request} title="Login with Google" onPress={() => { console.log("Prompting Google Sign-In"); promptAsync(); }} /> {userInfo && ( <Text style={{ marginTop: 20 }}>Welcome, {userInfo.name}!</Text> )} </View> ); }; export default GoogleLogin;
app.json配置
{ "expo": { "name": "towntaste", "slug": "towntaste", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "light", "splash": { "image": "./assets/splash.png", "resizeMode": "contain", "backgroundColor": "#ffffff" }, "assetBundlePatterns": ["**/*"], "plugins": [ [ "@react-native-google-signin/google-signin", { "iosUrlScheme": "com.googleusercontent.apps.468953728495-5cdcfubfhvrn76omjuldpdh66t8h66rf" } ] ], "ios": { "supportsTablet": true, "bundleIdentifier": "com.townTaste.app" }, "android": { "package": "com.townTaste", "versionCode": 1, "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#ffffff" } }, "web": { "favicon": "./assets/favicon.png" }, "extra": { "eas": { "projectId": "567ad302-63d0-4b12-a457-52484b479a04" } }, "owner": "caiolagreca", "scheme": "towntaste" } }
日志与错误信息
- Android端:点击Google登录后跳转空白页,返回登录页时日志:
LOG Google Auth Request: {"clientId": "468953728495-vekml2qel4cdjunjp27t9jokvqn7k5i2.apps.googleusercontent.com", "clientSecret": undefined, "codeChallenge": "JoP1_YjhxpNXNwzX5JqfeEFb16Y7mblT2Pykau17JXY", "codeChallengeMethod": "S256", "codeVerifier": "Hz651y7b1p5HmPF60mP2DPNQItdvrPuj6mdG3H8e8d8JMKX1AnScb9tu8HDGwhETpKnNY49AWVACATx31eD8stee6llB2LgJQyT1IZBUhNJABAYQW2BGr8mJd9T0BPTZ", "extraParams": {}, "prompt": undefined, "redirectUri": "exp://192.168.1.7:8081/--/auth/callback", "responseType": "code", "scopes": ["openid", "https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/userinfo.email"], "state": "wRFZ8nmYxv", "url": "https://accounts.google.com/o/oauth2/v2/auth?code_challenge=JoP1_YjhxpNXNwzX5JqfeEFb16Y7mblT2Pykau17JXY&code_challenge_method=S256&redirect_uri=exp%3A%2F%2F192.168.1.7%3A8081%2F--%2Fauth%2Fcallback&client_id=468953728495-vekml2qel4cdjunjp27t9jokvqn7k5i2.apps.googleusercontent.com&response_type=code&state=wRFZ8nmYxv&scope=openid+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email", "usePKCE": true} LOG Google Auth Response: {"type": "dismiss"}
- iOS端:点击登录后提示Access blocked: authorization error. Error 400: invalid_request; redirect_uri=exp://192.168.1.7:8081/auth/callback flowName=GeneralOAuthFlow,返回后日志同Android端的
{"type": "dismiss"}
Google Cloud重定向URI配置

解决步骤
1. 修正重定向URI匹配问题
从日志可见,Android端实际使用的redirectUri是exp://192.168.1.7:8081/--/auth/callback(带--路径),iOS端是exp://192.168.1.7:8081/auth/callback(不带--),但Google控制台配置的URI与这两个均不匹配。
需在Google开发者控制台的OAuth 2.0 客户端ID中,为对应客户端添加以下重定向URI:
- 开发环境:
exp://<你的本地IP>:8081/--/auth/callback(Android开发用)exp://<你的本地IP>:8081/auth/callback(iOS开发用)
- 生产环境(打包后):
- Android:
com.townTaste:/oauth2redirect/google - iOS:
com.googleusercontent.apps.468953728495-5cdcfubfhvrn76omjuldpdh66t8h66rf:/oauth2redirect/google(对应app.json中的iosUrlScheme)
- Android:
2. 统一redirectUri配置
在Google.useAuthRequest中,避免动态生成带IP的URI,改用Expo通用格式:
import { Platform } from 'react-native'; // ... const [request, response, promptAsync] = Google.useAuthRequest({ expoClientId: GOOGLE_WEB_CLIENT_ID, iosClientId: GOOGLE_IOS_CLIENT_ID, androidClientId: GOOGLE_ANDROID_CLIENT_ID, webClientId: GOOGLE_WEB_CLIENT_ID, redirectUri: Platform.select({ ios: Linking.createURL('/auth/callback'), android: Linking.createURL('/auth/callback'), web: 'https://towntaste.expo.dev/auth/callback' }), });
3. 检查客户端ID匹配
确保GOOGLE_ANDROID_CLIENT_ID、GOOGLE_IOS_CLIENT_ID分别对应Google控制台中Android客户端和iOS客户端的ID,GOOGLE_WEB_CLIENT_ID对应Web客户端ID,不要混淆。
4. 移除冲突插件配置
当前使用expo-auth-session方案,无需@react-native-google-signin/google-signin原生插件,两者可能冲突。移除app.json中的以下配置:
"plugins": [ [ "@react-native-google-signin/google-signin", { "iosUrlScheme": "com.googleusercontent.apps.468953728495-5cdcfubfhvrn76omjuldpdh66t8h66rf" } ] ],
5. 验证PKCE配置
Expo的Google.useAuthRequest默认启用PKCE,确保Google控制台的客户端ID配置中未禁用PKCE(Web、Android、iOS客户端默认支持)。
内容的提问来源于stack exchange,提问作者Caio Lagreca

