OAuth 그리고 OpenID Connect
Comparison of OAuth and OpenID Connect(OIDC)
나는 현재 Nomakse(가명)라는 프로젝트를 진행하며 서버 사이드에서 개발을 진행 중이다. 서비스의 특성 상 로그인 시스템이 필수적이었고 우리는 OAuth 프로토콜을 이용하여 Google과 Kakao에 인증을 위임하기로 결정하였다. 그러나 우리는 OAuth를 사용한 것이 아니라 OpenID 프로토콜을 사용한 것이었다!
What is OAuth 2.0?
OAuth란 권한 허가를 위한 산업 표준 프로토콜이다. OAuth는 Resource Owner가 Authorization Server에 인증 후, Third-party Application(Client)을 통해 Resource Server의 서비스(Resource)를 이용할 수 있게 해준다.
쉽게 말하면 내가 개발한 A라는 앱을 통해 “페이스북으로 로그인”을 하고 A라는 앱에서 내 페이스북 권한으로 뉴스 피드에 글을 쓸 수 있게 해준다는 것이다.
이는 OAuth 2.0을 정의하고 있는 RFC6749 Section 1.2에 매우 잘 설명 되어 있다. 굳이 번역하자니 길지도 않고 한번 읽어보는 것이 좋을 듯하다.
현재 OAuth는 2.0 버전이며 IETF OAuth Working Group에 의해 OAuth 2.1개발이 진행 중이다.
OpenID and OAuth
사실 OAuth를 활용하여 로그인 시스템을 구현하기 위해 자료를 찾던 중, OpenID와 OAuth의 개념이 혼용되어 사용되고 있다는 느낌을 받았다. 하지만 둘은 목적에서 차이를 보인다. OpenID는 인증(Authentication) 자체에 목적을 두고 있는 반면 OAuth는 인증 후 리소스 또는 API를 사용할 권한을 갖는 다는 것에 목적이 있다. 즉, 허가(Authorization)의 목적이라는 것이다. (Source: Naver D2)
때문에 OAuth도 권한을 얻기 위한 인증 과정이 존재한다. 아마 이 때문에 둘의 개념이 혼용되는 것이 아닌가 추측이 된다. 실제로 Google ID Platform의 OpenID Connect 서비스는 Google OAuth 2.0 API를 사용할 것을 명시하고 있다. OAuth API에 OpenID API가 포함되어 있다고 생각하면 될 듯하다. 실제로 그렇기도 하다.
Google’s OAuth 2.0 APIs can be used for both authentication and authorization. This document describes our OAuth 2.0 implementation for authentication, which conforms to the OpenID Connect specification, and is OpenID Certified(Source: Google ID Platform)
(edit)
글을 쓴 이후, OpenID에 대해 조금 더 조사를 하게 되었다. 그러던 중 OpenID에 대해 추가적으로 포스팅할 필요성을 느끼게되어 추가적으로 글을 적어보았다.
What is OpenID Connect?
OpenID의 풀네임은 사실 OpenID Connect(OIDC)이다. OIDC는 OAuth 2.0 프로토콜의 상위 계층에서 인증을 담당하는 프로토콜이다. 이것은 Client(Third-party APP)가 User의 신원을 검증하도록 해줌과 동시에 기본적인 User정보를 얻을 수 있게 해준다.
OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol. It enables Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server, as well as to obtain basic profile information about the End-User in an interoperable and REST-like manner. (Source: OpenID Connect Core)
OAuth프로토콜을 통해 리소스에 접근하기 위해서는 먼저 인증이 선행되어야 하므로 OIDC가 OAuth의 상위 계층에 위치하게 되는 것이다.
OIDC의 Flow는 아래와 같다. Figure3
의 예시와 함께 흐름도를 따라가다 보면 “Google로 로그인하기” 또는 “Facebook으로 로그인하기”를 해본 사람이라면 바로 이해할 것이다.
검색을 하다 발견한 포스팅이 하나 있는데 OIDC에 대해 정말 쉽고 자세하게 설명해 놓아서 한번 읽어보는 것이 좋을 듯하다. 흐름도 또한 공식 spec문서보다 조금 더 직관적인 것 같다. → What is OpenID Connect?
위의 OIDC프로토콜 Flow 통해 인증이 완료되면 ID Token이 발급된다. 일반적으로 Access Token도 함께 발급된다.
It should be noted that clients are not required to use the access token, since the ID Token contains all the necessary information for processing the authentication event. However, in order to provide compatibility with OAuth and match the general tendency for authorizing identity and other API access in parallel, OpenID Connect always issues the ID token along side an OAuth access token.(Source: User Authentication with OAuth 2.0 — OAuth.net)
ID Token은 JWT형식으로 구성된 End-user의 인증 정보를 담고 있는 보안 토큰이다. 주요 Claim으로는 iss
, sub
, aud
, exp
, iat
들 이 있으며 각각의 의미는 아래와 같다.(Source: OpenID Connect Core)
iss
: ID Token을 발급한 ID Provider(example: Google, Facebook).sub
: Client측에서 End-user를 식별할 수 있는 고유한(unique) 식별자.aud
: ID Token이 어떤 Client를 위해 발급된 것인지.exp
: 만료시점,iat
: 발급시점
위에서 언급했듯, OIDC는 OAuth 프로토콜의 인증을 담당하는 layer이다. 즉, OAuth프로토콜 위에서 구현되기 때문에 배포 또한 OAuth인프라와 함께 이루어지는 경우가 대부분이다. Google OAuth 2.0 API가 OpenID Connect API를 포함하고 있다고 한 것이 그 예시라고 할 수 있겠다.
OpenID Connect is built directly on OAuth 2.0 and in most cases is deployed right along with (or on top of) an OAuth infrastructure.(Source: User Authentication with OAuth 2.0 — OAuth.net)
User Authentication with OAuth
OAuth와 OpenID의 개념이 혼용되어 사용되는 느낌을 받았다고 언급했는데 정말 그런 경우가 많은가보다. 위키 백과에서는 OpenID vs. pseudo-authentication using OAuth section을 따로 두고 OAuth로 인증하는 것을 pseudo-authentication으로 칭하며 OAuth를 Authentication수단으로 사용 시 보안 결함이 발생할 수 있다고 설명하고 있다.
Because the identity provider typically (but not always) authenticates the user as part of the process of granting an OAuth access token, it’s tempting to view a successful OAuth access token request as an authentication method itself. However, because OAuth was not designed with this use case in mind, making this assumption can lead to major security flaws. (Source: Wikipedia)
조금 더 믿을 만한? 정보를 통해 보자면 OAuth.net에서 OAuth를 인증의 수단으로 사용시 발생하는 위험성에 대해 매우 매우 자세하게 기술해 놓았다. 요약하자면
- OAuth인증을 통해 얻어지는 액세스 토큰은 user 인증 이후에 발급되기 때문에 인증의 증거로써 간주되기 쉬우나 그 액세스 토큰은 Client(user가 아니라 인증을 요구하는 앱)에 대해 불투명하게(opaque) 설계되었기 때문에 user에 대한 어떠한 정보도 말해주지 않는다. 즉, user를 확인할 수단으로 사용할 수 없다.
- 또한 OAuth를 통해 발급받은 액세스 토큰을 사용하여 보호된 자원(리소스 또는 API)에 접근하는 것을 인증의 증거로 간주하는 경우가 있으나 이것 또한 잘못된 생각이다. 액세스 토큰은 꼭 인증(authentication)이 이루어져야만 얻을 수 있는 것이 아니기 때문이다. 리프레시 토큰이나 Assertion을 통해 액세스 토큰을 인증없이도 발급이 가능하다.
- 뿐만 아니라 액세스 토큰은 생명 주기가 있다. 토큰이 만료되는 시간이 있다는 말이다. 더 이상 인증이 필요한 user가 존재하지 않아도 액세스 토큰은 만료되지 않았기 때문에 보호된 자원에 접근할 수 있다. 따라서 보호된 자원에 접근은 인증의 증거가 될 수 없다.
- 이 외에도 액세스 토큰의 탈취 위험성, 액세스 토큰 Injection, audience를 제한하지 않아 다른 client의 액세스토큰으로 우리의 client(naive client)가 인증된 것처럼 위장하는 위험, 유효하지 않은 user정보 Injection이 존재한다.
— (Source: User Authentication with OAuth 2.0 - OAuth.net)—
위의 위험성들은 모두 OAuth를 인증(authentication, not authorization)의 수단으로 사용하기에 발생하는 것들이다. 설계의 목적에 맞게 OIDC 프로토콜을 따른다면 언급된 위험성들을 해소하며 안전하게 인증할 수 있다.
OIDC Example
간단한 예제를 통해 OIDC는 어떻게 위의 위험성을 해결하고, 인증하는지 보이도록 하겠다.
Google Sign-In API를 통해 ID token이 발급되었다고 하자.
백엔드 서버가 없는 클라이언트 앱이라면 이 시점에서 이미 인증이 끝난 것이다. 그러나 백엔드 서버가 있고 자체적으로 로그인 시스템이 존재한다면 인증 과정을 아래의 몇 단계가 추가될 것이다.
- ID token을 서버로 전달.
- 서버 측에서 ID token검증.
- ID token으로부터 user 정보를 뽑아내 DB와 비교.
우리는 2번 과정을 살펴볼 것이고 아래는 간단한 서버 측 검증 코드이다.(Source: Google Sign In)
const {OAuth2Client} = require('google-auth-library');
const client = new OAuth2Client(CLIENT_ID);
async function verify() {
const ticket = await client.verifyIdToken({
idToken: token,
audience: CLIENT_ID, // Specify the CLIENT_ID of the app that accesses the backend
// Or, if multiple clients access the backend:
//[CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3]
});
const payload = ticket.getPayload();
const email= payload['email'];
// If request specified a G Suite domain:
// const domain = payload['hd'];
}
verify().catch(console.error);
굵은 줄 표시된 두 줄을 유심히 보자.
- ID token의
audience
를 제한함으로써 유효한 Client로부터 온 토큰인지 검증한다. - ID token으로부터 user 정보를 추출한다.
이 것만으로도 위에 언급된 위험성이 상당수 해결되는 것을 잘 생각해보면 알 수 있다.
이외에도 google-auth-library
는 추가적인 검증 과정을 거쳐 ID token의 유효성을 검사한다. 더 자세한 사항은 이 문서를 참고하면 나와있다.
Conclusion
처음에 우리가 사용해야 할 기술은 당연히 OAuth라 생각하고 라이브러리를 사용하였다. 그러나 분명 OAuth와 OpenID는 목적이 다른 프로토콜이고 그에 따라 오용 시, 보안 취약점이 발생할 수 있다. 다행히 구현하고 보니 그게 OIDC였던 것이다…OAuth로 꾸역꾸역 구현한 것이 아니라 다행이라는 생각이다.
사실 글을 쓰다 보니 내용이 의도한 방향과 다르게 완전히 탈선해 버린 느낌이다. OAuth와 OpenID를 비교하는 것이 글의 주제가 아니었던 것 같은데.. OAuth만으로도 프로토콜 flow에 관해 쓸 내용이 꽤 많다. 그러나 내가 개발하면서 헤메었던 부분을 위주로 작성하려다 보니 주제가 이 쪽으로 흘러온 것 같다.
이번 포스팅을 쓴 이유는 OIDC를 사용하여 user인증 후에 내가 직접 조사하며 설계한 token-based 인증 시스템 설계를 글로 정리하기 위함이다. 이번 인증 시스템을 구현하면서 Docker와 Podman 그리고 Redis 더 나아가 리프레시 토큰의 존재 타당성까지 공부하게 되었다.(리프레시 토큰의 존재가 당연하다 생각할 수 있는 부분이었지만 내겐 당연하지 않았다.). 하나씩 차근차근 포스팅을 할 예정이다.
ref:
- RFC6749 - The OAuth 2.0 Authorization Framework
- OpenID Connect Core - openid.net
- 편의성을 높인 ID 인증 관리: OIDC(OpenID Connect)가 주목 받는 이유 - SAMSUNG SDS
- What is OpenID Connect? - nordicapis.com
- User Authentication with OAuth 2.0 — OAuth.net
- OAuth와 춤을 - Naver D2
- OpenID Connect - Google ID Platform
- Authenticate with a backend server - Google ID Platform
- OAuth - Wikipedia