ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • GET google api access token (with gapi, no firebase)
    FrontEnd/Bootcamp 2021. 9. 24. 03:40

    *** 이 포스팅은 작성자의 경험을 바탕으로 작성되었으며, 사실과 다른 부분이 있을 수 있습니다 ***

    클라이언트 React, 서버 Node.js(Express)를 사용합니다.


    요약

    Google은 현재 인증 방법 업데이트 중.

     

    로그인은 새로운 라이브러리 https://accounts.google.com/gsi/client

    (문서: https://developers.google.com/identity/gsi/web/guides/client-library),

    google api 접근을 위한 OAuth 2.0은 기존 라이브러리 https://apis.google.com/js/api.js 를 사용한다.

    (문서: https://developers.google.com/identity/protocols/oauth2)

     

    로그인 방식이 업데이트되면서 scope 추가 부분이 없어져 권한 허용 요청을 별도로 처리해야 하는데,

    OAuth 2.0 부분은 아직 업데이트가 되지 않아서 기존 라이브러리를 사용해야 한다.

     

    기존 라이브러리를 사용하면서 access token을 재발급받고 싶은 경우 아래처럼 접근할 수 있다.

    await window.gapi.get.getAuthInstance().currentUser.get().reloadAuthResponse();

    react를 사용하면서 기존 라이브러리를 사용해서 OAuth를 처리한다면 npm의 react-google-login 패키지를 사용하면 간편하다. 다만 access token 재발급 가능 유무를 확인하지 못해 실제로 사용해보지는 않았다.

    firebase를 사용하면 간단하게 google api accessToken을 획득할 수 있으나 유효기간이 1시간이라는 단점이 있다.


     

    유저의 수면 및 운동 정보를 분석해주는 웹 애플리케이션 프로젝트를 진행하면서,

    google 로그인과 google fitness api 접근 권한을 처리할 필요가 있었다.

     

    firebase로는 한계가 있다

    처음에는 firebase 로그인 + addScope 조합을 시도해보았다.

    firebase를 이용해서 google 로그인을 처리하면 addScope만으로 accessToken을 획득할 수 있기 때문.

    (firebase@8.9.1 기준) 먼저 provider 설정 시에 해당 부분을 추가한다.

    const provider = new firebase.auth.GoogleAuthProvider();
    
    provider.addScope('https://www.googleapis.com/auth/contacts.readonly');
    firebase.auth().signInWithRedirect(provider);

    이후 getRedirectResult에서 아래와 같은 형태로 credential을 확인할 수 있다.

    const { user, credential } = await firebase.auth().getRedirectResult();
    
    console.log(credential);

    문제는 firebase에서 제공하는 google api accessToken은 유효기간이 1시간이며, 별도의 refreshToken을 제공하지 않는다는 것.

     

    firebase에서 제공하는 토큰에는 3종류가 있는데,

     

    idToken

    유저의 기본적인 정보(이름, 이메일, 프로필 사진 등)를 얻을 수 있는 토큰

     

    refreshToken

    위의 idToken을 갱신하기 위한 토큰. firebase를 사용 중이고 유저가 로그인된 상태라면, refreshToken을 이용할 필요 없이 getIdToken(true)로 처리하면 갱신된 idToken을 획득할 수 있다.

     

    accessToken

    google api 접근을 위한 토큰. 유효기간은 1시간. 로그인 직후에만 얻을 수 있다. 갱신 불가능.

     

    *토큰 관련해서는 이쪽 스택오버플로우 답변에서 보다 상세한 정보를 확인할 수 있다: retrieve-google-access-token-after-authenticated-using-firebase-authentication

     

    처음에는 이름을 보고 access-refresh가 짝일 것이라고 생각해서 해당 엔드포인트를 찾아보았다.

    엔드포인트: https://developers.google.com/identity/toolkit/reference/securetoken/rest/v1/token

    access_token이라는 말에 희망을 가졌으나, 실제로 접근을 시도해보면 반환해주는 access_token는 firebase의 idToken이다.

     

    또 idToken을 이용해 signInWithCredential으로 재로그인하는 아래와 같은 방법도 있었지만, 내 경우에는 이렇게 했을 때 새로운 accessToken이 나오지 않았다. 해당 필드가 생략된 채 반환된다.

     

    How to Refresh Google AccessToken in Firebase? #AskFirebase

    I am attempting to build a web app that will be integrated with and installed into Google Drive. The user will be able to create and share my app's files in their drive. I am trying to write it us...

    stackoverflow.com

     

    한참을 헤맨 끝에 아래의 답변을 확인한다.

     

    How to get providers access tokens from Firebase authenticated user?

    I am using Firebase to authenticate with GitHub, Twitter and Facebook and I know I can get the provider access token upon authenticating like so firebase.auth().signInWithPopup(provider).then(func...

    stackoverflow.com

    Unfortunately, Firebase Auth does not manage OAuth access tokens for users. After OAuth sign in via popup/redirect, the access token is returned but no new access tokens will be returned afterwards.

    안타깝게도 Firebase Auth는 유저의 OAuth access token을 관리하지 않는다. 팝업이나 리다이렉트를 통해 OAuth 로그인이 완료된 직후에만 access token이 반환되고, 이후에는 새로운 access token이 반환되지 않는다.

     

    수동동기화 기능을 구현하려면 refresh token이나 new access token이 필수적이기 때문에,

    firebase를 포기하고 google OAuth를 직접 연결하기로 했다.

     

     

    Sign in With Google

    google identity 페이지에 가보니 기존 방식인 google Sign-in은 더 이상 업데이트되지 않을 것이라고 한다.

    대신 새로 도입된 Sign In With Google을 사용하라고 되어 있다.

     

    기존 api 라이브러리도 아래 라이브러리로 대체되었음이 명시되어 있다.

    처리를 위해서 구글에서 제공한 예시 코드리액트에서 sciprt 태그를 처리하는 방법을 참고해서 아래와 같이 함수를 작성했다.

    function initGoogle() {
      window.google.accounts.id.initialize({
        client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID,
        callback,
      });
    
      window.google.accounts.id.renderButton(
        document.getElementById("google-login-button"),
        { theme: "outline", size: "large" },
      );
    
      window.google.accounts.id.prompt();
    }

    그리고 스크립트 로드가 완료되면 처리하도록 useEffect에서 핸들러로 달아주었다.

    useEffect(() => {
      const script = document.createElement("script");
    
      script.src = "https://accounts.google.com/gsi/client";
      script.async = true;
      script.addEventListener("load", initGoogle);
    
      document.body.appendChild(script);
    
      return () => {
        script.removeEventListener("load", initGoogle);
        document.body.removeChild(script);
      };
    }, []);

     

    문제는 새로 도입된 Sign In With Google에는 addScope에 해당하는 기능이 없다는 것.

     

    Google API 클라이언트 ID 가져오기  |  Sign In With Google  |  Google Developers

    경고 : 이 데이터는 Google 사용자 데이터 정책에 따라 제공됩니다. 정책을 검토하고 준수하십시오. 그렇게하지 않으면 프로젝트 또는 계정이 정지 될 수 있습니다. 이 페이지는 Cloud Translation API를

    developers.google.com

    정보 보호차원에서 바람직한 방향이긴 하다. 권한을 추가하기 위해서 문서를 더 읽어보니, Migrating from Google Sign-In 필드가 있다.

    기존에 사용하던 access token과 scope가 모두 ID token으로 대체되었다고.

    위에서 사용했던 window.google.accounts 객체에는 id와 oauth2라는 속성이 있다. oauth2를 이용해서 ID token에 권한 옵션을 추가하려고 시도해보니

    window.google.accounts.oauth2.initTokenClient({
      client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID,
    });

    아직 실험적인 기능이라는 에러가 뜬다.

    그렇다면 방법은 기존 OAuth 2.0으로 인증하는 것 뿐. 확인을 위해 왼쪽의 OAuth 2.0으로 가보니

    아래처럼 스크립트 태그로 라이브러리를 호출하는 방식이 설명되어 있고, 아직 업데이트가 되지 않았다.

    권한 처리를 위해 기존의 gapi를 그대로 써야한다면 로그인에만 새로운 방식을 도입할 이유도 없어보인다.

     

     

    Google OAuth 2.0

    기존 플로우에는 옵션이 여러 가지가 있는데, 크게는 Server Side(HTTP/REST)와 Client Side로 나눌 수 있다.

    둘다 유저에게 로그인 및 권한 허용창을 보여준다는 점에서는 같지만, 이후 진행과정과 응답 형식이 다르다.

     

    1. Server Side

    https://developers.google.com/identity/protocols/oauth2/web-server#httprest_1

    - access_type을 offline으로 지정하면 이후 refresh token을 얻을 수 있다.

    - offline으로 지정시 반환되는 authorization_code를 token으로 바꾸기 위해서는 client_secret이 필요하다. (이 특성 때문에 환경변수가 노출되는 클라이언트 단에서는 사용할 수 없다.)

    - redirect_uri가 구글 콘솔에 등록되어 있어야 하고, token 교환 요청을 보내는 uri는 처음 인증과정에서 지정했던 것과 같은 redirect_uri여야 한다. 그렇지 않은 경우, 둘다 구글 콘솔에 등록된 uri더라도 mismatch_error가 발생한다.

     

    2. Client Side

    https://developers.google.com/identity/protocols/oauth2/javascript-implicit-flow

    - 구글에서 제공하는 라이브러리(script tag 형태)가 있다.

    - 리액트에서 사용할 경우 public/index.html에 추가하거나 별도의 로드 작업이 필요하다.

    - 스크립트 로드가 완료되면 window.gapi로 라이브러리 사용이 가능하다.

    - 로그인이 완료되면 gapi.auth2.getAuthInstance().currentUser.get().reloadAuthResponse()로 전역에서 토큰을 포함한 유저 정보에 접근할 수 있다.

     

    처음에는 reloadAuthResponse 메소드를 확인하지 못해서, access token 갱신을 위해서는 무조건 refresh token을 발급받아야 한다고 생각했다.

    하지만 위에서 언급한 redirect_uri와 client_secret 문제로 클라이언트 단에서 직접 인증을 처리할 수 없어, 고민 끝에 다음 두 가지 방법을 떠올렸다.

     

    1. 로그인만 서버사이드 렌더링을 한다.

    2. 유저가 로그인 요청을 보내면 클라이언트에서 redirect_uri를 서버단으로 지정, 서버에서 인증 및 토큰 작업을 모두 처리(refresh token은 별도의 저장공간에 저장)한 후 클라이언트로 리다이렉트한다.

     

    둘다 탐탁지 않아 혹시 다른 방법이 있을까 검색하다가 이런 이슈를 발견한다.

    stackoverflow: how-to-refresh-expired-google-sign-in-logins

     

    No way of obtaining refresh _token from gapi.auth2.authorize api from client side · Issue #643 · google/google-api-javascript-

    Hi, Am using gapi.auth2.authorize api to allow multiple user accounts being signed in. but it's callback function doesn't provide a refresh_token. Requirement is to keep the users until the...

    github.com

    토큰을 재발급 받고 싶다면 아래 메소드를 이용하면 된다고.

    await window.gapi.get.getAuthInstance().currentUser.get().reloadAuthResponse();

     

    처음 목적이었던 수동동기화 기능은 유저가 로그인 되어있는 상황에만 필요하므로, 이 메소드면 충분하다는 결론을 내리고 이 방법대로 구현했다.

    gapi를 사용한다면 서버단에서 idToken을 verify 할 때도 google-auth-libraray를 사용해야 한다. 사용법은 firebase-admin과 거의 유사하다.

     const { OAuth2Client } = require("google-auth-library");
     
     const googleClient = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);
     
     const loginTicket = await googleClient.verifyIdToken({
       idToken, // 클라이언트에서 getAuthResponse, reloadAuthResponse 등으로 얻을 수 있다.
       audience: process.env.GOOGLE_CLIENT_ID,
     });

    위의 방법은 현재 작업중인 것으로 보이는 window.google.accounts.oauth2가 정식으로 출범하기 전까지만 유효할 것 같다.

    새로운 방법이 나온다면 다시 마이그레이션을 거쳐야할 듯.

    그전에 firebase에서 access token을 refresh 하는 방법을 제공해주지 않을까 하는 기대를 해본다!

    'FrontEnd > Bootcamp' 카테고리의 다른 글

    바닐라코딩 10기 프렙, 부트캠프, 취직 후기  (4) 2022.01.09

    댓글

Designed by Tistory.