상세 컨텐츠

본문 제목

React Native (Expo)에 구글 가입 기능 추가하기

개발

by 호박너구리의 블로그 2021. 12. 22. 18:30

본문

- 종종 프론트엔드 삽질기를 보내드리고 있습니다. 간단하지만 재미있는 개발 삽질기를 받아보고 싶으시다면, 호박너구리 개발일지 뉴스레터를 구독해 주세요:)



사실 웹(React)에서 Google API로 구글 계정 연동 기능을 만드셨던 분들은, React Native에서도 비슷하게 구현하면 된다고 생각하실 수 있습니다. 사실 저도 그런 생각을 갖고 있었는데요. 그러나 App이어서 그런지, web에서 url redirect 방식을 사용했던 것과 방식이 조금 다르더라고요.

저는 Expo를 통해서 만들고 있는데, 검색해보니 많은 글들은 expo가 deprecated 시킨 Google이나, GoogleSignIn 패키지를 이용한 내용을 다루고 있었습니다. Expo 는 현재 AuthSession 이라는 패키지 사용을 장려하고 있는데요. 오늘은 Expo에서 장려하는 AuthSession 을 통해 google oauth 를 구현하는 방법을 살펴보겠습니다!

 

1. 구글 API 등록하고 clientId 준비하기

이미 웹에서 google account 연동을 해보셨거나, clientId를 갖고 계신 분들은 이번 스텝을 건너뛰셔도 됩니다.

 

1. 우선 구글 API를 사용해본 적이 없는 분들이라면, 구글 api에 먼저 계정을 등록해야 합니다.

2. 이후 '사용자 인증 정보' 라는 탭을 클릭합니다.

3. '사용자 인증 정보 만들기' 라는 버튼을 클릭하고, 목록 중에서 'OAuth 클라이언트 ID' 를 선택합니다.

4. 자신이 타겟하는 플랫폼에 맞게 애플리케이션 유형을 선택합니다. (web, iOS, android 등)

- 여기서 안드로이드의 경우 sha-1 인증서가 필요한데요. 이는 Expo 문서에서 안내하는 방식대로 하면 됩니다

- 안드로이드의 패키지 이름 입력창에는 app.json 파일의 android.package 와 동일한 값을 넣어야 합니다

- iOS의 번들 ID 입력창에는 app.json 파일의 ios.bundleIdentifier 와 동일한 값을 넣어야 합니다.

- web의 경우 '승인된 자바스크립트 원본' 파트(JS origin)에 'https://auth.expo.io'와 'https://localhost:19006'를 넣어주세요.

- web의 경우 '승인된 리디렉션 URI' (Authorized redirect URIs)에 'https://auth.expo.io/@{계정 이름}/{서비스 이름}'와 https://localhost:19006를 넣어주세요. (계정과 서비스 이름은 expo 로그인 하면 등록 가능)

 

5. 생성이 완료되면 페이지에서 clientID를 확인할 수 있습니다.

 

위 모든 과정은 해당 Expo 문서에 자세히 설명되어 있으며, 추가 설정이 필요한 부분이 있어서 각 유형 설정에 대해서는 문서를 꼭 읽어보시기를 추천드립니다.

 

아래는 app.json 예시 코드입니다 (만약 기본 탬플릿에 ios, android 등이 없다면 직접 추가해주시면 됩니다!)

{
  "expo": {
    "name": "xxx",
    "slug": "xxx",
    "version": "1.0.0",
    "orientation": "portrait",
    "icon": "./src/assets/images/brand/icon.png",
    "scheme": "myapp",
    "userInterfaceStyle": "automatic",
    "splash": {
      "image": "./src/assets/images/brand/splash.png",
      "resizeMode": "contain",
      "backgroundColor": "#ffffff"
    },
    "updates": {
      "fallbackToCacheTimeout": 0
    },
    "assetBundlePatterns": [
      "**/*"
    ],
    "ios": {
      "bundleIdentifier": "com.xxx.xxx",
      "buildNumber": "1.0.0",
      "supportsTablet": true
    },
    "android": {
      "package": "com.xxx.xxx",
      "versionCode": 1
    }
  }
}

 

 

2. 리액트 네이티브 코드 작성하기 with Expo

여기 과정까지 모두 Expo 문서에 적혀있는데, 계정 정보를 받아오는 부분에 대한 설명이 없어서 저는 예시 코드와 한 가지 다른 옵션을 주어 구현했습니다.

 

우선 expo에서는 AuthSession을 이용하기 위해서 peer dependency를 설치하라고 합니다.

yarn add expo-application

 

문서에서는 다음과 같은 예시 코드를 제공합니다.

(각 clientId에는 애플리케이션 유형에 맞는 자신의 clientId를 넣어야 합니다)

import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import * as Google from 'expo-auth-session/providers/google';
import { Button } from 'react-native';

WebBrowser.maybeCompleteAuthSession();

export default function App() {
  const [request, response, promptAsync] = Google.useAuthRequest({
    expoClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
    iosClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
    androidClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
    webClientId: 'GOOGLE_GUID.apps.googleusercontent.com',
  });

  React.useEffect(() => {
    if (response?.type === 'success') {
      const { authentication } = response;
      }
  }, [response]);

  return (
    <Button
      disabled={!request}
      title="Login"
      onPress={() => {
        promptAsync();
        }}
    />
  );
}

 

그리고 실제로 다음과 같은 코드를 동작하면 response와 request가 제대로 동작하는 것을 확인할 수 있습니다. response의 authentication에 콘솔을 찍어보면 내부에 accessToken과 같이 필요한 값이 있는 것을 볼 수 있죠.

 

그러나 저는 google oauth를 구현하는 과정에서 accessToken을 사용하지 않고, 코드나 idToken을 활용해서 계정을 받아왔는데요. 그러기 위해서는 한 가지 옵션을 더 추가했습니다. 

const [request, response, promptAsync] = Google.useAuthRequest({
    expoClientId: GOOGLE_OAUTH_CONFIG.webApplicationClientId,
    iosClientId: GOOGLE_OAUTH_CONFIG.iosClientId,
    androidClientId: GOOGLE_OAUTH_CONFIG.androidClientId,
    webClientId: GOOGLE_OAUTH_CONFIG.webApplicationClientId,
    responseType: 'id_token'
})

 

responseType에는 디폴트로 'token'이 들어가 있는데, 받고 싶은 값에 따라서 'id_token'과 'code'를 넣어줄 수도 있습니다.

(저는 타입스크립트를 사용해서 실제로는 아래와 같은 코드를 작성했습니다)

const [request, response, promptAsync] = Google.useAuthRequest({
    expoClientId: GOOGLE_OAUTH_CONFIG.webApplicationClientId,
    iosClientId: GOOGLE_OAUTH_CONFIG.iosClientId,
    androidClientId: GOOGLE_OAUTH_CONFIG.androidClientId,
    webClientId: GOOGLE_OAUTH_CONFIG.webApplicationClientId,
    responseType: ResponseType.IdToken
})

 

그러면 response의 params에서 idToken 값을 확인할 수 있습니다.

그리고 clientId와 idToken 값이 있으면, 구글 계정 정보를 받아올 수 있죠!

 

또한 Expo는 구글 뿐만 아니라 AuthSession을 사용하는 모든 과정에서 다음과 같은 꼭 유의해야하는 중요한 규칙이 있다고 설명하였으니 참고해주세요! (참고용일 뿐, 제가 작성한 예시 코드만 봐도 괜찮습니다)

- web popup을 무시하기 위해 WebBrowser.maybeCompleteAuthSession()를 사용하세요. 만약 사용하지 않으면 팝업 윈도우는 닫히지 않을 것입니다.
- AuthSession.makeRedirectUri() 으로 리디렉션을 생성하세요. 다양한 플랫폼을 지원합니다.
- AuthSession.useAuthRequest() 를 사용하여 요청하세요. 그래야 모바일 브라우저가 블록하지 않을 것입니다.

 

※ 만약 Firebase 로 구현하는 경우

firebase로 구현하는 경우에는, 다음과 같은 코드를 참고하시면 됩니다!

import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { ResponseType } from 'expo-auth-session';
import * as Google from 'expo-auth-session/providers/google';
import { initializeApp } from 'firebase/app';
import { getAuth, GoogleAuthProvider, signInWithCredential } from 'firebase/auth';
import { Button } from 'react-native';

// Initialize Firebase
initializeApp({
  /* Config */
});

WebBrowser.maybeCompleteAuthSession();

export default function App() {

  const [request, response, promptAsync] = Google.useIdTokenAuthRequest(
    {
      clientId: 'Your-Web-Client-ID.apps.googleusercontent.com',
      },
  );

  React.useEffect(() => {
    if (response?.type === 'success') {
      const { id_token } = response.params;
      
      const auth = getAuth();
      const provider = new GoogleAuthProvider();
      const credential = provider.credential(id_token);
      signInWithCredential(auth, credential);
    }
  }, [response]);

  return (
    <Button
      disabled={!request}
      title="Login"
      onPress={() => {
        promptAsync();
        }}
    />
  );
}

 

 

3. idToken 으로 구글 계정 정보 가져오기

NodeJS 백엔드를 쓰는 경우 여기 내용부터는 백엔드에서 구현하는 것을 장려합니다. (자바스크립트를 사용한다면 한 곳에서 사용해도 괜찮습니다)

 

이제 idToken 까지 받아왔습니다. 마지막으로 해당 토큰 값을 활용해서 구글 계정 정보를 받아오면 되는데요. 그러기 위해서는 다음과 같은 패키지를 설치해야 합니다.

npm install google-auth-library

 

그리고 다음과 같은 코드를 작성하면 끝입니다!

(함수명, 클래스 사용 여부 등은 자유롭게 하시면 됩니다)

import { OAuth2Client } from 'google-auth-library'

export default class OAuthService {
  static getAccountFromIdToken = async (
    clientId: string, 
    idToken: string
  ): Promise<{
    id: string
    email: string
  } | null> => {
    const client = new OAuth2Client(clientId);
    const ticket = await client.verifyIdToken({
      idToken: idToken,
      audience: clientId
    });
    const payload = ticket.getPayload();
    return (payload && payload.email)
      ? {
        id: payload.sub,
        email: payload.email
      }
      : null
  }
}

 

방금 설치한 라이브러리에서 OAuth2Client 를 사용하여 clientId로 client를 만들고,

verifyIdToken을 통해 ticket을 생성하면 ticket의 payload 에서 계정 정보를 확인하실 수 있죠.

 

여기서 sub라는 값은 계정의 id와 같은 값이라고 생각하시면 됩니다!

해당 로직 및 각 필드에 대한 값은 이 파일을 참고하시면 됩니다!

 

그럼 모두 해피코딩하세요:)

728x90

관련글 더보기

댓글 영역

  • 프로필 사진
    2022.02.03 07:11
    혹시 app.json에 android.package 항목이 없으면 어떡하나요?
    • 프로필 사진
      직접 추가하시면 됩니다! (꼭 지금 아니더라도 어차피 나중에 실제로 빌드할 떄에도 추가가 필요할거에요) 위에 예시 코드 추가하여 글 수정해둘게요 :)
  • 프로필 사진
    2022.03.28 15:49
    좋은 글 감사드려요. id_token 값은 잘 가져오는데
    const credential = provider.credential(id_token);
    TypeError: provider.credential is not a function. (In 'provider.credential(id_token)', 'provider.credential' is undefined)
    이 부분에서 오류가 자꾸 납니다. 구글링해도 답이 없네요 ㅠㅠ 혹시 아실까요 ??
  • 프로필 사진
    2022.04.26 20:18
    혹시 로그인하고 403 error : disallowed useragent 가 뜨는데 해당 오류가 안뜨셨나요??
  • 프로필 사진
    2022.05.13 02:06
    firebase로 구현했을때 Expo go에서는 잘되는데 빌드 후에는
    "Something went wrong trying to finish signing in."
    이 오류가 나서 눈물이 나네요...