[React Native] Expo Android iOS 구글 로그인 성공하기 without firebase
개발 환경 > Firebase 사용 안함 Expo 사용 React Native 0.77 이상 @react-native-google-signin/google-signin 라이브러리 사용 들어가기 전에 RN EXPO로 어플 만들다보면, AI는 공식문서에 있는 라이브러리
개발 환경
Firebase 사용 안함 Expo 사용 React Native 0.77 이상 @react-native-google-signin/google-signin 라이브러리 사용
들어가기 전에
RN EXPO로 어플 만들다보면, AI는 공식문서에 있는 라이브러리로 코드를 알려주는데 그러다보면 auth-session으로 알려준다
또는 구글링 하다보면 auth-session으로 구글 로그인을 진행한 사람이 있는데, 이건 옛날 게시물일 경우가 허다함
이거 보면 " You cannot use Google auth-session in EXPO Go anymore "
처음 auth-session 으로 진행 하던 중 redirect_uri_match 오류가 계속 생겨서 5시간 동안 삽질하다가 같은 오류의 expo 이슈를 발견하게 되었다
더 이상 Google auth-session은 EXPO Go 에서 사용할 수 없다고 되어있음
따라서 다시 정신을 부여잡고
사용하기 로 함
Google Cloud
https://console.cloud.google.com/ 우선 여기서 프로젝트 생성을 해야함
만드는 방법은 Web Client랑 iOS Client 만들면 되는데 이건 구글링하면 금방나오니 패스
Web Client
승인된 JavaScript 원본 : https://auth.expo.io
승인된 리디렉션 URI : https://auth.expo.io/{@expo에 등록된 나(whoami 명령어 있음)}/{slug}
혹시 모르니 서버 리다이렉트 uri도 넣는거 추천!
iOS Client
번들 Id는 : app.json 보면 bundleIdentifier이나, package 보면 com.xxxxx.xxxx 이렇게 되어 있는게 있는데 그거 넣으면 된다 !
모든 설정이 끝났으면
... 생략// ++ 이 부분 추가하세요.GoogleSignin.configure({ webClientId: GOOGLE_CLIENT_ID_WEB, iosClientId: GOOGLE_CLIENT_ID_IOS, scopes: ['email', 'profile'], offlineAccess: false, forceCodeForRefreshToken: false,});android는 GoogleSignIn에서 webClientId로 받아온다
만약에 지금까지 그냥 npx expo start로 실행했다면, 이제 안될거다
GoogleSignIn은 expo를 지원 안하기 때문에 development build 해야함
expo로 개발하면 환경설정 많이 안할 줄 알았는데 결국 하게 되는 현실
development build
# 먼저 개발 빌드 생성npx expo install expo-dev-client
# iOS development build 생성eas build --profile development --platform ios
# Android development build 생성eas build --profile development --platform AndroidGoogle Sign-In 코드 예제
// hooks/useGoogleAuth.tsimport { useState, useEffect } from 'react';import { GoogleSignin, statusCodes } from '@react-native-google-signin/google-signin';
interface GoogleUser { id: string; name: string; email: string; photo?: string;}
export const useGoogleAuth = () => { const [user, setUser] = useState<GoogleUser | null>(null); const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null);
useEffect(() => { // Google Sign-In 초기 설정 GoogleSignin.configure({ // iOS 클라이언트 ID iosClientId: '584178392628-loms8bh6egsfr1q2fl9om43i9i51530g.apps.googleusercontent.com', // 웹 클라 이언트 ID (Firebase 콘솔에서 확인) webClientId: 'YOUR_WEB_CLIENT_ID.apps.googleusercontent.com', // 추가 설정 offlineAccess: true, hostedDomain: '', forceCodeForRefreshToken: true, });
// 현재 로그인 상태 확인 checkCurrentUser(); }, []);
const checkCurrentUser = async () => { try { const isSignedIn = await GoogleSignin.isSignedIn(); if (isSignedIn) { const userInfo = await GoogleSignin.getCurrentUser(); if (userInfo?.user) { setUser({ id: userInfo.user.id, name: userInfo.user.name || '', email: userInfo.user.email, photo: userInfo.user.photo || undefined, }); } } } catch (error) { console.error('현재 사용자 확인 실패:', error); } };
const signIn = async () => { try { setLoading(true); setError(null);
// Google Play Services 확인 (Android) await GoogleSignin.hasPlayServices(); // 로그인 실행 const userInfo = await GoogleSignin.signIn(); if (userInfo?.user) { const googleUser: GoogleUser = { id: userInfo.user.id, name: userInfo.user.name || '', email: userInfo.user.email, photo: userInfo.user.photo || undefined, }; setUser(googleUser); return googleUser; } } catch (error: any) { console.error('Google 로그인 실패:', error); if (error.code === statusCodes.SIGN_IN_CANCELLED) { setError('로그인이 취소되었습니다.'); } else if (error.code === statusCodes.IN_PROGRESS) { setError('로그인이 진행 중입니다.'); } else if (error.code === statusCodes.PLAY_SERVICES_NOT_AVAILABLE) { setError('Google Play Services를 사용할 수 없습니다.'); } else { setError('로그인 중 오류가 발생했습니다.'); } } finally { setLoading(false); } };
const signOut = async () => { try { setLoading(true); await GoogleSignin.signOut(); setUser(null); } catch (error) { console.error('로그아웃 실패:', error); setError('로그아웃 중 오류가 발생했습니다.'); } finally { setLoading(false); } };
const revokeAccess = async () => { try { setLoading(true); await GoogleSignin.revokeAccess(); setUser(null); } catch (error) { console.error('액세스 철회 실패:', error); setError('액세스 철회 중 오류가 발생했습니다.'); } finally { setLoading(false); } };
return { user, loading, error, signIn, signOut, revokeAccess, isSignedIn: !!user, };};
// components/GoogleSignInButton.tsximport React from 'react';import { TouchableOpacity, Text, View, ActivityIndicator } from 'react-native';import { useGoogleAuth } from '../hooks/useGoogleAuth';
export const GoogleSignInButton: React.FC = () => { const { user, loading, error, signIn, signOut, isSignedIn } = useGoogleAuth();
if (loading) { return ( <View className="flex-row items-center justify-center bg-white border border-gray-300 rounded-lg px-6 py-3"> <ActivityIndicator size="small" color="#4285F4" /> <Text className="ml-2 text-gray-600">처리 중...</Text> </View> ); }
if (isSignedIn && user) { return ( <View className="bg-white border border-gray-300 rounded-lg p-4"> <Text className="text-gray-800 font-semibold">안녕하세요, {user.name}님!</Text> <Text className="text-gray-600 text-sm">{user.email}</Text> {error && <Text className="text-red-500 text-sm mt-2">{error}</Text>} <TouchableOpacity onPress={signOut} className="mt-3 bg-red-500 rounded-lg px-4 py-2" > <Text className="text-white text-center font-semibold">로그아웃</Text> </TouchableOpacity> </View> ); }
return ( <TouchableOpacity onPress={signIn} className="flex-row items-center justify-center bg-white border border-gray-300 rounded-lg px-6 py-3 shadow-sm" disabled={loading} > {/* Google 아이콘 */} <View className="w-5 h-5 mr-3"> <Text>🔵</Text> {/* 실제로는 Google 아이콘 이미지 사용 */} </View> <Text className="text-gray-700 font-semibold">Google로 로그인</Text> {error && ( <View className="mt-2"> <Text className="text-red-500 text-sm">{error}</Text> </View> )} </TouchableOpacity> );};
// app/(auth)/login.tsx - 로그인 화면 예제import React from 'react';import { View, Text, SafeAreaView } from 'react-native';import { GoogleSignInButton } from '../../components/GoogleSignInButton';
export default function LoginScreen() { return ( <SafeAreaView className="flex-1 bg-white"> <View className="flex-1 justify-center items-center px-6"> {/* 앱 로고 */} <View className="mb-12"> <Text className="text-4xl font-bold text-center mb-2" style={{ color: '#58CC02' }}> 🌱 쑥쑥약속 </Text> <Text className="text-gray-600 text-center"> 부모와 아이가 함께 성장하는{'\n'}약속 관리 앱 </Text> </View>
{/* Google 로그인 버튼 */} <View className="w-full"> <GoogleSignInButton /> </View>
{/* 추가 정보 */} <View className="mt-8"> <Text className="text-gray-500 text-sm text-center"> 로그인 후 부모와 아이 계정을{'\n'}연결할 수 있습니다 </Text> </View> </View> </SafeAreaView> );}필요한 패키치 설치
# Google Sign-In 패키지 설치npm install @react-native-google-signin/google-signin
# Development client 설치 (이미 설치했다면 생략)npx expo install expo-dev-clientEAS Build Profile 설정
이건 쉽게 생각하면, expo로 시작했으니, react native cli 초기 설정을 한다고 생각하면 됨
// 예시{ "cli": { "version": ">= 0.52.0" }, "build": { "development": { "developmentClient": true, "distribution": "internal", "ios": { "resourceClass": "m1-medium" } }, "preview": { "distribution": "internal" }, "production": {} }}이렇게 설정하면 firebase 없이 구글 로그인 성공 !