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을 사용할 수 있습니다.
Implicit Grant
OAuth 2.0 프로토콜은 애플리케이션의 유형에 따라 여러 가지 인가 방식을 제공하는데요. 이 글을 처음 작성한 2019년에는 React나 Vue, Angular 같은 자바스크립트 라이브러리로 개발한 SPA(single page application)에서 Implicit Grant를 많이 사용했습니다. 다른 OAuth 2.0 방식보다 구현이 단순했기 때문에 이 글의 실습도 Implicit Grant를 기준으로 작성했습니다.
Implicit Grant는 현재 새 애플리케이션에 권장되지 않습니다. 지금 SPA를 만든다면 액세스 토큰을 브라우저 URL로 직접 전달하지 않는 Authorization Code + PKCE를 사용하세요. 아래 내용은 기존 애플리케이션의 동작을 이해하기 위한 참고 자료로 봐 주세요.
sequenceDiagram
accTitle: Implicit Grant로 구글 액세스 토큰을 받는 흐름
accDescr: 사용자가 자바스크립트 애플리케이션에서 구글 연결을 시작하면 애플리케이션이 사용자를 구글 인가 서버로 보낸다. 사용자가 로그인하고 권한을 허용하면 구글은 리디렉션 URI로 액세스 토큰을 직접 전달한다. 애플리케이션은 이 토큰으로 Google API를 호출한다.
actor User as 사용자
participant App as 자바스크립트 앱
participant Google as 구글 인가 서버와 API
User->>App: 구글 연결 시작
App->>Google: 인가 요청<br/>(response_type=token)
Google-->>User: 로그인과 권한 동의 화면
User->>Google: 로그인하고 권한 허용
Google-->>App: redirect_uri로 access token 전달
App->>Google: Google API 요청 + access token
Google-->>App: API 응답
OAuth 2.0의 implicit grant 방식 인가는 크게 다음과 같은 흐름으로 진행됩니다.
- 사용자는 구글에 로그인 후에 애플리케이션에서 요청하는 권한을 확인하고, 해당 권한을 허용해줍니다.
- 구글 인가 서버는 사용자를 사전에 등록된 리디렉션 URI(redirect URI)로 보내면서 액세스 토큰(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은 인가 응답으로 무엇을 받을지 정합니다.
여기서 token으로 설정하면 위에서 설명드린 implicit grant처럼 리디렉션 URI의 프래그먼트에 액세스 토큰이 직접 담겨 돌아옵니다.
토큰 엔드포인트에 보내는 grant_type과는 다른 파라미터예요.
scope은 사용자의 어떤 데이터와 어떤 작업에 대한 권한을 요청하는지를 나타내는데, 여기서는 People API를 읽기 전용으로 호출이 필요하다고 설정하였습니다.
다른 구글 API를 호출이 필요한 경우에는 아래 공식 러페런스를 참고 바랍니다.
쿼리 스트링을 다루기 위해서 사용한
qs라이브러리에 대해서는 별도 포스팅을 참고 바랍니다.
액세스 토큰 획득
사용자가 구글 인가 서버 URL을 통해 애플리케이션에서 요청하는 권한을 허용해주면, 구글 인가 서버는 redirect_uri로 액세스 토큰을 보내줍니다.
액세스 토큰 값은 redirect_uri의 hash 부분에 포함되어 있기 때문에 DOM의 Location API를 통해 어렵지 않게 읽을 수 있습니다.
이 액세스 토큰이 있어야 구글 People API를 호출할 수 있기 때문에, 액세스 토큰이 없는 경우에는 인가 서버 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 호출
액세스 토큰 값이 확보되면, 구글 API를 호출하는 것은 매우 간단한 일입니다.
People API를 호출할 때, 액세스 토큰 앞에 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 방식에 대한 좀 더 자세한 내용은 아래 공식 레퍼런스를 참고바라겠습니다.
This work is licensed under CC BY 4.0