Logo

OAuth 2.0으로 구글 API 호출하기

검색과 지메일, 연락처, 캘린더, 드라이브, 포토, 유튜브 등 우리는 거의 매일 구글의 서비스를 이용하면서 살고 있다고 해도 과언이 아니죠? 구글은 이렇게 다양한 제품에 걸쳐서 관리되고 있는 데이터를 사용자의 허락을 받고 접근할 수 있도록 Google APIs를 제공하고 있는데요.

이번 글에서는 OAuth 2.0을 통해 사용자의 동의를 구하고 구글 API를 호출하는 방법에 대해서 알아보겠습니다.

OAuth 2.0이란?

먼저 OAuth 2.0이 생소하신 분을 위해서, 과연 OAuth 2.0가 무엇인지 간단하게 개념부터 짚고 가겠습니다. OAuth 2.0은 한마디로, 사용자가 어떤 서비스에 저장된 자신의 데이터를 다른 서비스에서 접근할 수 있도록 허락해주는 프로토콜입니다. 구글 뿐만 페이스북, 트위터 등 많은 글로벌 서비스들이 OAuth 1.0에 이어 OAuth 2.0을 채택하면서 OAuth는 인가 분야에서 거의 표준이 되어 가고 있습니다.

구글의 OAuth 2.0 권한 허용 화면

예를 들어, 구글에 저장되어 있는 사용자의 연락처와 연동이되는 애플리케이션을 만든다고 가정을 해봅시다. 애플리케이션 개발자로서 구글에 저장되어 있는 사용자의 연락처 데이터를 어떻게 읽어올 수 있을까요?

가장 원시적인 방법은 사용자에게 직접 구글에 들어가서 자신의 연락처를 내려 받아 우리 애플리케이션에 올리라고 하는 것입니다. 하지만 이런 방식으로는 여러 장치를 통해 시시각각 바뀌는 사용자의 연락처 정보를 실시간으로 업데이트를 받을 수가 없습니다. 또한 많은 사용자들은 직접 연락처를 업로드하기가 너무 불편해서 애초에 이 애플리케이션 쓰려고 하지도 않을 것입니다.

두 번째 방법은 사용자에게 구글 계정의 이메일과 암호를 알려달라고 해서 사용자 대신 구글에 로그인 해서 사용자의 연락처를 읽어오는 것입니다. 하지만 이럴 경우 기술적으로 사용자의 연락처 뿐만 아니라 지메일이나 구글 드라이브에 저장된 민감한 개인 정보까지도 모두 접근할 수 있게되기 때문에 이는 보안적으로 매우 위험한 발상입니다. 설사 사용자가 순순히 구글 로그인 정보를 넘겨준다고 해도, 애플리케이션 개발자 입장에서 이렇게 취합된 모든 구글 계정의 이메일과 암호를 어떻게 안전하게 관리할지 고민이 많아지게 됩니다.

이렇게 타 서비스에 저장된 사용자 데이터를 우리가 개발하는 애플리케이션으로 끌고 오는 것은 생각보다 쉽지않은 일입니다. 이러한 문제를 해결해주는 OAuth 2.0을 사용하면 애플리케이션은 타 서비스에 저장된 사용자 데이터를 접근해도 되는지 사용자에게 명시적으로 허락을 받을 수 있습니다. 사용자 입장에서도 자신의 모든 데이터가 아닌 특정 데이터의 특정 작업에 대해서만 접근을 허용할 수 있기 때문에 안심하고 OAuth 2.0을 사용할 수 있습니다.

Implicit Grant

OAuth 2.0 프로토콜은 애플리케이션의 유형에 따라 여러 가지 인가 방식을 제공하는데요. React나 Vue, Angular 등과 같은 자바스크립트 라이브러리로 개발되는 SPA(single oage application)에서는 implicit grant 방식이 많이 사용됩니다. implicit grant 방식이 다른 OAuth 2.0 방식에 비해서 간단하게 때문에, 본 포스팅에서는 implicit grant 방식으로 실습을 진행해보려고 합니다.

Implict Grant 인가 흐름

OAuth 2.0의 implicit grant 방식 인가는 크게 다음과 같은 흐름으로 진행됩니다.

  1. 사용자는 구글에 로그인 후에 애플리케이션에서 요청하는 권한을 확인하고, 해당 권한을 허용해줍니다.
  2. 구글 인가 서버는 사용자를 사전에 등록된 redirect uri로 리다이렉트 시키면서 access token을 애플리케이션에 보내줍니다.
  3. 애플리케이션은 이 access token을 이용해서 구글 API를 호출합니다.

실습 프로젝트

구글의 People API를 사용하여 사용자의 연락처의 그룹 목록을 출력하는 간단한 React 애플리케이션을 작성해보겠습니다. 실습 프로젝트에서는 OAuth 2.0이 어떻게 동작하는지 이해하기 위해서 구글 인가 서버(Authorization Server)와 구글 People API 서버를 직접 호출해보도록 하겠습니다. 실제 프로젝트에서는 반드시 구글에서 언어별로 제공하는 SDK를 사용하는 것이 강하게 권장됩니다.

구글 클라이언트 등록

애플리케이션으로서 구글 API를 호출하려면 제일 먼저, 구글에 자신의 애플리케이션을 클라이언트로 등록하고 클라이언트 아이디(client id)를 발급 받아야 합니다.

구글 API 콘솔에서 새 프로젝트를 생성하고 사용자 인증 정보 만들기에서 OAuth 클라이언트 ID를 선택합니다. 그 다음, 애플리케이션 유형을 웹 애플리케이션으로 클라이언트 ID를 만들고, 승인된 리디렉션 URI를 실습 애플리케이션의 URL(ex. http://localhost:3000/, https://google-oauth.stackblitz.io/)로 지정합니다.

인가 서버 URL

OAuth 2.0의 시작은 사용자가 구글 인가 서버(Authorization Server)에 접속하여 로그인 후, 애플리케이션에서 요청하는 권한을 허용해주는 것입니다. 이를 위해서는 먼저 사용자가 접속해야하는 구글 인가 서버의 URL을 만들어야 하는데요. 여러 개의 쿼리 파라미터가 있지만, 필수 파라미터인 client_id, redirect_uri, response_type, scope은 반드시 지정해줘야 합니다.

import qs from "qs";

const CLIENT_ID = "<자신의 애플리케이션이 발급받은 클라이언트 아이디>";
const AUTHORIZE_URI = "https://accounts.google.com/o/oauth2/v2/auth";

const queryStr = qs.stringify({
  client_id: CLIENT_ID,
  redirect_uri: window.location.href,
  response_type: "token",
  scope: "https://www.googleapis.com/auth/contacts.readonly",
});

const loginUrl = AUTHORIZE_URI + "?" + queryStr;

client_id는 구글 클라이언트를 등록하면 누구나 바로 얻을 수 있고, redirect_uri는 구글 API 콘솔에서 직접 설정한 내 애플리케이션의 URL입니다. 실습 애플리케이션은 페이지가 하나 밖에 없는 SPA이기 때문에, 현재 URL을 redirect_uri로 설정하였습니다. response_type은 어떤 OAuth 방식을 사용하는지를 결정하는데, token로 설정하면 위에서 설명드린 implicit grant 방식이 적용됩니다. scope은 사용자의 어떤 데이터와 어떤 작업에 대한 권한을 요청하는지를 나타내는데, 여기서는 People API를 읽기 전용으로 호출이 필요하다고 설정하였습니다.

다른 구글 API를 호출이 필요한 경우에는 아래 공식 러페런스를 참고 바랍니다.

쿼리 스트링을 다루기 위해서 사용한 qs 라이브러리에 대해서는 별도 포스팅을 참고 바랍니다.

Access Token 획득

사용자가 구글 인가 서버 URL을 통해 애플리케이션에서 요청하는 권한을 허용해주면, 구글 인가 서버는 redirect_uri로 access token을 보내줍니다. access token 값은 redirect_uri의 hash 부분에 포함되어 있기 때문에 DOM의 Location API를 통해 여럽지 않게 읽을 수 있습니다. 이 access token이 있어야 구글 People API를 호출할 수 있기 때문에, access token이 없는 경우에는 인가 서버 URL(loginUrl)로 사용자를 리다이렉트 시킵니다.

import React, { useState, useEffect } from "react";
import qs from "qs";

export default () => {
  const { access_token } = qs.parse(window.location.hash.substr(1));

  if (!access_token) {
    window.location.assign(loginUrl);
    return null;
  }

  // 생략
};

구글 API 호출

access token 값이 확보되면, 구글 API를 호출하는 것은 매우 간단한 일입니다. People API를 호출할 때, access token 앞에 Bearer 문자열만 붙여서, Authorization 헤더로 넘기면 됩니다.

import React, { useState, useEffect } from "react";
const PEOPLE_URI = "https://people.googleapis.com/v1/contactGroups";

export default () => {
  // 생략

  const [contactGroups, setContactGroups] = useState([]);

  useEffect(() => {
    fetch(PEOPLE_URI, {
      headers: { Authorization: "Bearer " + access_token },
    })
      .then((response) => response.json())
      .then((data) => setContactGroups(data.contactGroups));
  }, [access_token]);

  return (
    <>
      <h2>Contact Groups</h2>
      <ul>
        {contactGroups &&
          contactGroups.map(({ resourceName, name, memberCount }) => (
            <li key={resourceName}>
              {name} ({memberCount})
            </li>
          ))}
      </ul>
    </>
  );
};

전체 코드

마치면서

이상으로 Google APIs를 직접 호출해보면서 OAuth 2.0이 어떻게 동작하는지 간단하게 살펴보았습니다. OAuth 2.0이 실제로 구현을 해보면 의외로 까다로운 부분이 많아서 배울 부분이 많은 것 같습니다. 구글 API 호출을 위해 OAuth 2.0을 사용하는 방법과 implicit grant 방식에 대한 좀 더 자세한 내용은 아래 공식 레퍼런스를 참고바라겠습니다.