Back to Notes

Implementing Google Login in Expo React Native: Handling Expo Go Limitations

Adding Google Sign-In to a React Native app is a common requirement.

In this guide, we use the @react-native-google-signin/google-signin library to implement Google authentication. Since this library relies on native modules, it works seamlessly in APK builds and production, but does not work in Expo Go, as Expo Go doesn’t include these native dependencies.

To handle this limitation gracefully, we’ll structure our code so that Google Sign-In works in development builds and APKs, while hiding the Google Sign-In button during local development in Expo Go. This avoids runtime errors during development while keeping authentication fully functional in real builds.

The Challenge: Expo Go Limitations

Expo Go is a convenient way to test your app during development, but it has limitations when it comes to native modules. The @react-native-google-signin/google-signin library requires native code that isn't available in Expo Go, which means:

Infographic explaining React Native Google Login

The Solution: Conditional Authentication

The best approach is to implement a dual authentication strategy:

This way, developers can still test the app in Expo Go using email/password authentication, while Google Sign-In is available in preview and production builds.

Prerequisites

Before we begin, make sure you have:

  1. An Expo React Native project set up
  2. Google OAuth credentials configured in the Google Cloud Console
  3. The @react-native-google-signin/google-signin package installed

Step 1: Install Dependencies

First, install the required package:

1npm install @react-native-google-signin/google-signin

Step 2: Configure app.json

Add the Google Sign-In plugin to your app.json:

1{
2 "expo": {
3 "plugins": ["@react-native-google-signin/google-signin"]
4 }
5}

After adding the plugin, you'll need to rebuild your app (development build or production build) for the native changes to take effect.

Step 3: Environment Variables

Set up the following environment variables in your .env file:

1EXPO_PUBLIC_GOOGLE_CLIENT_ID_WEB=your-web-client-id
2EXPO_PUBLIC_GOOGLE_CLIENT_ID_IOS=your-ios-client-id
3EXPO_PUBLIC_GOOGLE_CLIENT_ID_ANDROID=your-android-client-id

These client IDs come from your Google Cloud Console OAuth 2.0 credentials. Make sure to configure separate OAuth clients for web, iOS, and Android platforms.

Android Client ID Setup:

When creating an Android client ID in Google Cloud Console, it will ask for a SHA1 fingerprint. You can get this by running:

1eas credentials

Then select "preview" to get the SHA1 fingerprint for your development/preview builds.

Production Android Client ID:

For production releases, create another Android client ID in Google Cloud Console and add the SHA1 fingerprint by selecting "production" in eas credentials. You do not need to add this production Android client ID to your .env or app config.

Step 4: Configure Google Sign-In in Your Login Screen

In your login component, we need to conditionally configure Google Sign-In only when not running in Expo Go. We'll use Constants.appOwnership to detect the environment.

1// login.jsx
2import { useEffect, useState } from "react"
3import { TouchableOpacity, Text } from "react-native"
4import Constants from "expo-constants"
5import { AntDesign } from "@expo/vector-icons"
6import { useRouter } from "expo-router"
7import { toast } from "react-native-toast-message"
8import { googleLogin } from "../utils/auth"
9
10export default function LoginScreen() {
11 const [loading, setLoading] = useState(false)
12 const router = useRouter()
13
14 // Configure Google Signin only when not in Expo Go
15 useEffect(() => {
16 const isExpoGo = Constants.appOwnership === "expo"
17
18 if (!isExpoGo) {
19 try {
20 const {
21 GoogleSignin,
22 } = require("@react-native-google-signin/google-signin")
23 GoogleSignin.configure({
24 webClientId: process.env.EXPO_PUBLIC_GOOGLE_CLIENT_ID_WEB,
25 iosClientId: process.env.EXPO_PUBLIC_GOOGLE_CLIENT_ID_IOS,
26 androidClientId: process.env.EXPO_PUBLIC_GOOGLE_CLIENT_ID_ANDROID,
27 })
28 } catch (e) {
29 console.warn("Google Signin configure failed (likely in Expo Go):", e)
30 }
31 }
32 }, [])
33
34 const handleGoogleLogin = async () => {
35 const isExpoGo = Constants.appOwnership === "expo"
36
37 if (isExpoGo) {
38 toast.info("Google Login is not available in Expo Go")
39 return
40 }
41
42 setLoading(true)
43 try {
44 const result = await googleLogin()
45 if (result.success) {
46 router.replace("/(tabs)")
47 } else {
48 toast.error(result.error || "Google login failed")
49 }
50 } catch (error) {
51 toast.error(error.message || "Something went wrong")
52 } finally {
53 setLoading(false)
54 }
55 }
56
57 const isExpoGo = Constants.appOwnership === "expo"
58
59 return (
60 <View>
61 {/* Email/Password login form */}
62 {/* ... your email/password form here ... */}
63
64 {/* Conditionally render Google login button */}
65 {!isExpoGo && (
66 <TouchableOpacity
67 className="mb-6 flex-row items-center justify-center space-x-3 rounded-lg bg-white py-4"
68 onPress={handleGoogleLogin}
69 disabled={loading}
70 >
71 <AntDesign name="google" size={24} color="black" />
72 <Text className="font-nunito-bold ml-2 text-base text-black">
73 Continue with Google
74 </Text>
75 </TouchableOpacity>
76 )}
77 </View>
78 )
79}

Key Points:

Step 5: Implement the Google Login Function

Create a utility function or add it to your auth context that handles the actual Google Sign-In flow:

1// AuthContext.jsx
2import { GoogleSignin } from "@react-native-google-signin/google-signin"
3import mobileApiClient from "../utils/api"
4
5export const googleLogin = async () => {
6 try {
7 setError(null)
8
9 // Import here to avoid issues if module is not linked
10 const {
11 GoogleSignin,
12 } = require("@react-native-google-signin/google-signin")
13
14 // Check if Google Play Services are available (Android)
15 await GoogleSignin.hasPlayServices()
16
17 // Sign in with Google
18 const userInfo = await GoogleSignin.signIn()
19
20 // Get the idToken for backend authentication
21 const { idToken } = await GoogleSignin.getTokens()
22
23 if (!idToken) {
24 throw new Error("Could not get ID token from Google")
25 }
26
27 // Send idToken to your backend for verification
28 const { user, token } = await mobileApiClient.googleLogin(idToken)
29 setUser(user)
30
31 // Load additional user data (non-blocking)
32 loadHabits().catch(() => {
33 // Don't block login on habit loading failure
34 })
35
36 // Schedule notifications if enabled (non-blocking)
37 const { notificationsEnabled } = useSettingsStore.getState()
38 if (notificationsEnabled) {
39 ensureDailyReminderScheduled(20, 0).catch(() => {
40 // Ignore scheduling errors here
41 })
42 }
43
44 return { success: true }
45 } catch (error) {
46 console.error("Google Login Error:", error)
47 let errorMessage = error.message
48
49 // Handle native module errors typically thrown by GoogleSignin
50 if (error.code) {
51 const {
52 statusCodes,
53 } = require("@react-native-google-signin/google-signin")
54
55 if (error.code === statusCodes.SIGN_IN_CANCELLED) {
56 errorMessage = "Sign in cancelled"
57 } else if (error.code === statusCodes.IN_PROGRESS) {
58 errorMessage = "Sign in is already in progress"
59 } else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) {
60 errorMessage = "Play services not available"
61 }
62 }
63
64 setError(errorMessage)
65 return { success: false, error: errorMessage }
66 }
67}

Error Handling:

The code handles common Google Sign-In error scenarios:

Step 6: Backend API Integration

Your backend should have an endpoint that accepts the Google ID token and verifies it. Here's an example API client method:

1// utils/api.js
2class ApiClient {
3 async googleLogin(idToken) {
4 const data = await this.request("/auth/google", {
5 method: "POST",
6 body: JSON.stringify({ token: idToken }),
7 })
8
9 if (data.token) {
10 await this.setToken(data.token)
11 }
12
13 return data
14 }
15}
16
17const mobileApiClient = new ApiClient()
18
19export default mobileApiClient

Backend Verification:

Your backend should:

  1. Verify the ID token with Google's token verification endpoint
  2. Extract user information from the verified token
  3. Create or update the user in your database
  4. Return a JWT token for your app to use in subsequent requests

Best Practices

  1. Graceful Degradation: Always provide an alternative authentication method (email/password) that works in Expo Go

  2. Error Handling: Implement comprehensive error handling for all Google Sign-In scenarios, including network errors and user cancellations

  3. Loading States: Show appropriate loading indicators during the authentication process

  4. Token Management: Securely store the authentication token returned from your backend

  5. User Feedback: Provide clear feedback to users about what's happening and why Google Sign-In might not be available in certain environments

Testing Strategy

Common Issues and Solutions

Issue: Google Sign-In button doesn't appear in Expo Go

Issue: "Google Signin configure failed" warning

Issue: Play Services not available (Android)

Issue: Sign-in cancelled error

Conclusion

Implementing Google Sign-In in Expo React Native requires careful handling of Expo Go limitations. By conditionally configuring and rendering the Google Sign-In feature based on the app environment, you can create a seamless authentication experience that works across all build types.

The key takeaways:

With this approach, your authentication system will work smoothly whether developers are using Expo Go for quick testing or development builds for full feature testing.

Back to Notes