Logo

구글 OpenID Connect 사용법

최근에는 아이디와 비밀번호 입력없이도 구글이나 페이스북과 같은 대형 플랫폼을 통해서 로그인 할 수 있는 서비스들을 많이 볼 수 있습니다. 이번 포스팅에서는 이렇게 사용자 인증을 다른 서비스에 위임하기 위해서 사용되는 프로토콜인 OpenID Connect에 대해서 알아보겠습니다.

OpenID Connect란?

예전에는 사용자 데이터를 서비스에서 직접 관리하는 경우가 많았지만, 개인 정보가 유출되는 보안 사고가 잇달아 발생함에 따라, 요즘에는 사용자 데이터를 자체적으로 보관하는 것 자체가 부담스러운 작업이 되어가고 있습니다. 이 때문에, 최근에는 사용자 데이터를 안전하게 관리할 역량이 있는 구글과 같은 대형 플랫폼을 통해서 로그인 처리를 하는 서비스들이 점점 많아지고 있는 추세입니다.

거의 왠만한 사용자는 구글이나 페이스북, 트위터에 이미 가입을 해서 계정이 있기 때문에 이러한 대형 플랫폼 통해 로그인 처리를 하면 사용자 데이터를 직접 관리하지 않고도 사용자를 인증을 구현할 수 있습니다. 사용자 입장에서도 서비스마다 아이디와 패스워드를 기억할 필요가 없고 귀찮은 회원 가입 절차도 거치지 않을 수 있어서 전통적인 로그인 방법보다 편리하게 느껴지는 장점이 있습니다.

OpenID Connect(OIDC)는 현재 구글 뿐만 아니라 많은 글로벌 플랫폼들이 다른 서비스에게 사용자 인증을 제공을 위해 사용하고 있는 프토콜입니다. 우리는 애플리케이션 개발자로서 OIDC 프로토콜을 이용하여 이러한 대형 플랫폼을 통해 사용자를 안전하게 인증하고 사용자의 동의 하에 해당 플랫폼에서 관리되고 있는 개인 정보에 접근할 수 있습니다.

OAuth와의 차이

OpenID Connect는 기본적으로 OAuth 프로토콜을 기반으로 작동하는 프로토콜이기 때문에 기술적으로 사용하는 방법이 매우 유사하지만 사용하는 목적에서는 큰 차이가 있습니다.

OIDC는 인증(Authentication)을 위해서 사용하고, OAuth는 인가(Authroization)를 위해서 사용합니다. 다시말해, OIDC는 다른 플랫폼을 통해서 사용자가 누구인지를 확인하기 위해서 사용하는 반면에, OAuth는 해당 플랫폼에 저장된 사용자의 데이터에 접근하기 위해서 사용합니다.

따라서, OAuth의 목적은 다른 플랫폼의 다른 API를 호출하기 위해서 access token을 확보하는 것입니다. 하지만 OIDC의 경우, 사용자의 개인 정보가 담긴 id token을 확보라는 다른 목적을 가지고 있습니다.

구글 OAuth 2.0 설정

구글 OpenID Connect를 사용하려면 OAuth 2.0과 마찬가지로 구글 API 콘솔에서 클라이언트 등록 후에 클라이언트 아이디(client id)를 발급 받아야 합니다.

이에 대해서는 별도로 OAuth 2.0 관련 포스팅에 자세히 설명해 두었으니 참고바라겠습니다.

인가 서버 URL

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

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 id_token",
  scope: "openid profile email",
  nonce:
    Math.random().toString(36).substring(2, 15) +
    Math.random().toString(36).substring(2, 15),
});

const loginUrl = AUTHORIZE_URI + "?" + queryStr;

client_id는 구글 클라이언트를 등록하면 누구나 바로 얻을 수 있고, redirect_uri는 구글 API 콘솔에서 직접 설정한 내 애플리케이션의 URL입니다. 실습 애플리케이션은 페이지가 하나 밖에 없는 SPA이기 때문에, 현재 URL을 redirect_uri로 설정하였습니다. response_type은 어떤 OAuth 방식을 사용하는지를 결정하는데, token id_token로 설정하면 보통 자바스크립트 애플리케이션에서 사용하는 implicit grant 방식이 적용됩니다. scope은 구글 사용자의 어떤 데이터에 대한 권한을 요청하는지를 나타내는데, openid는 반드시 필요하며, profileemail을 추가할 수 있습니다.

ID Token 획득

사용자가 구글 인가 서버 URL을 통해 애플리케이션에서 요청하는 권한을 허용해주면, 구글 인가 서버는 redirect_uri로 access token과 id token을 보내줍니다. access token과 id token 값은 redirect_uri의 hash 부분에 포함되어 있기 때문에 DOM의 Location API를 통해 여럽지 않게 읽을 수 있습니다. OIDC에서는 access token에는 큰 관심이 없기 때문에, 사용자 정보가 담긴 id token 값만 읽어오면 됩니다. id token이 없는 경우에는 인가 서버 URL(loginUrl)로 사용자를 리다이렉트 시켜서 사용자가 구글 로그인을 할 수 있도록 합니다.

import React from "react";
import qs from "qs";

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

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

  // 생략
};

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

ID Token 디코딩

확보된 id token 값은 JSON Web Tokens(JWT) 방식으로 인코딩이 되어 있는 문자열이므로 먼저 디코딩을 해줘야 합니다. jwt-decode와 같은 라이브러리를 사용해서 이 id token을 디코딩하면 여러 가지 사용자 데이터가 담긴 객체를 얻을 수 있습니다. 이 중 사용자의 이메일과 이름, 프로필 사진을 읽어서 화면에 출력해보겠습니다.

import React from "react";
import jwtDecode from "jwt-decode";

export default () => {
  // 생략

  const { email, name, picture } = jwtDecode(id_token);

  return (
    <>
      <h2>Google Profile</h2>
      <dt>email</dt>
      <dd>{email}</dd>
      <dt>name</dt>
      <dd>{name}</dd>
      <dt>picture</dt>
      <dd>
        <img src={picture} />
      </dd>
    </>
  );
};

JWT(JSON Web Token)에 대한 자세한 설명은 관련 포스팅을 참고 바랍니다.

전체 코드

마치면서

이상으로 Google API를 직접 호출해보면서 OpenID Connect가 어떻게 동작하는지 간단하게 살펴보았습니다. 실습 프로젝트에서는 내부 동작을 이해하기 위해서 직접 API를 호출하였지만, 실제 프로젝트에서는 반드시 구글에서 언어별로 제공하는 SDK를 사용하셔야 합니다.

구글 OpenID Connect에 대한 좀 더 자세한 내용은 아래 공식 레퍼런스를 참고바라겠습니다.