版本
- expo 52
- react-native 0.76.1
- @react-native-google-signin/google-signin 13.1.0
設定 OAuth 同意畫面
1.到 Google Cloud Console 建立專案
2.進入憑證頁面
3.建立憑證 > 新增 OAuth 用戶端 ID,此時會提示你需要先設定同意畫面


4.User Type 選擇外部

5.接著填寫 APP 的資訊,後面步驟全部跳過即可。

創建 OAuth 用戶端 ID
只需要創建 Web 和 iOS
回到憑證,建立 OAuth 用戶端 ID

Web
- 應用程式類型選擇 Web
- 已授權的JavaScript來源和已授權的重新導向URI都設為
http://localhost:8081
,這是 expo 預設的 web uri

iOS
繫結編號就是 app.json 中 ios.bundleIdentifier

安裝 react-native-google-signin
安裝 @react-native-google-signin/google-signin
npx expo install @react-native-google-signin/google-signin
在 app.json 的 plugins 中加上:
{
"expo": {
"plugins": [
[
"@react-native-google-signin/google-signin",
{
"iosUrlScheme": "com.googleusercontent.apps._some_id_here_"
}
]
]
}
}
iosUrlScheme 就是 iOS網址通訊協定,在剛剛建立的 iOS OAuth 用戶端ID裡面可以找到。

實作Google登入按鈕
修改 app.json 中的 scheme
{
"expo": {
"scheme": "your app name"
}
}
新增一個 GoogleSigninButton.tsx
import { StyleSheet, Pressable, View } from 'react-native'
import { Image, Text } from 'tamagui'
import { useTranslation } from 'react-i18next'
// ...
import { useAuth } from '@/hooks'
export const GoogleSigninButton = () => {
const { signIn } = useAuth()
//...
return (
<Pressable style={[styles.container, { backgroundColor: color.primary }]} onPress={signIn}>
<View style={styles.iconContainer}>
<Image source={require('../../assets/images/google.png')} style={styles.icon} />
</View>
<Text style={styles.text} color={color.text} fontSize={18}>{t('SignInWithGoogle')}</Text>
</Pressable>
)
}
路由權限驗證
useAuth.ts
import { useContext } from 'react'
import { GoogleSignin } from '@react-native-google-signin/google-signin'
import { AuthContext } from '@/context'
import { useAuthStore } from '@/hooks'
GoogleSignin.configure({
webClientId: process.env.EXPO_PUBLIC_WEB_CLIENT_ID,
})
export const useAuth = () => {
const { setToken } = useAuthStore()
const signIn = async () => {
try {
await GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true })
const userInfo = await GoogleSignin.signIn()
console.log('userInfo', userInfo)
// send idToken to server for verification
const token = ....
setToken(token)
} catch (error) {
console.error(error)
}
}
const signOut = async () => {
try {
await GoogleSignin.signOut()
setToken(null)
} catch (error) {
console.error(error)
}
}
return { signIn, signOut }
}
app/_layout.tsx
<AuthProvider>
<Stack>
<Stack.Screen name="login" options={{ headerShown: false }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
</Stack>
</AuthProvider>
AuthProvider.tsx
import { PropsWithChildren } from 'react'
import { AuthContext } from '@/context'
import { useAuthStore } from '@/hooks'
export const AuthProvider = ({ children }: PropsWithChildren) => {
const { token, setToken } = useAuthStore()
return (
<AuthContext.Provider value={{ token, setToken }}>
{children}
</AuthContext.Provider>
)
}
需要登入才能訪問的路由記得加上權限驗證,比如:
// (tabs)/_layout.tsx
import React from 'react'
import { Tabs, Redirect } from 'expo-router'
import { useTranslation } from 'react-i18next'
import { TabBar } from '@/components'
import { useAuthStore } from '@/hooks'
export default function TabLayout() {
const { t } = useTranslation()
const { token } = useAuthStore()
if (!token) {
return <Redirect href="/login" />
}
return (
<Tabs
backBehavior="history"
screenOptions={{ headerShown: false }}
tabBar={(props) => <TabBar {...props} />}
>
<Tabs.Screen name="index" options={{ title: t('Home') }} />
</Tabs>
)
}