OAuth 깊게 이해하기

OAuth 깊게 이해하기
Photo by Matt Artz / Unsplash

소셜 로그인을 구현하다보면 OAuth 를 자연스레 마주치게된다. 구글, 카카오, 페이스북, 네이버... 수 많은 연동을 하지만 연동 가이드를 따를 뿐 OAuth를 왜 사용하는지에 대해서는 골똘히 고민해본 적은 없었다. 이번 글에서는 OAuth 가 없던 세상부터 OAuth가 어떻게 타 서비스와의 연동을 이룰 수 있도록하는지 알아볼 것이다. 그리고 끝으로 OAuth 위에서 작동하는 OIDC 가 무엇인지, 왜 사용하는 지에 대해서 알아볼 것이다.

(글 읽기에 앞서, 대부분의 내용은 RFC 6749에 명시된 것을 적당하게 풀어서 이야기하고 있음을 밝힌다. 결국 OAuth 을 이해하기 위해서는 직접 구현과 더불어 해당 문서의 정독이 필요하다.)

타 서비스와 연동하기

OAuth 에 대해 알아보기 전에, OAuth 가 탄생하게된 배경에 대해서 알아볼 필요가 있다. 한번 "유튜브 분석기" 라는 어플리케이션을 만든다고 가정해보자. 이 어플리케이션은 다음과 같은 일을 해야한다. (여기서 타 서비스는 "유튜브 분석기" 가 된다)

  • 사용자의 유튜브 기록을 조회하고 이를 분석한다.

해당 기능을 구현하기 위해서는 "사용자의 유튜브 기록"을 조회할 수 있어야한다. 그런데 OAuth 가 존재하지 않는 세상이라면 어떻게 "사용자의 유튜브 기록"을 획득할 수 있을까?

  • 사용자에게 구글 이메일 / 비밀번호를 받는다.
    • 그리고 그 이메일 / 비밀번호를 서버는 저장해둔다.
  • 어플리케이션에서는 해당 구글 이메일 / 비밀번호로 대신 로그인하고 정보를 구글에서 획득한다.

그렇다. 직접 구글에 들어갈 수있는 권한인 아이디와 패스워드 를 사용자는 우리 어플리케이션에 제공해야한다. 듣기만해도 사용자 그리고 서비스 운영자 모두에게 굉장히 찝찝한 일이다. 이런 방식으로 로그인을한다면 다음과 같은 문제가 발생할 수 있다.

  • 사용자의 권한을 과도하게 획득한다.
    • 나쁜 마음을 먹은 서비스 운영자가 사용자의 계정으로 나쁜 짓을 할 수 있다.
  • 사용자의 민감 정보 노출된다.
  • 사용자는 비밀번호를 변경하는 방법 외에 해당 권한을 revoke 할 방법이 존재하지 않는다.
  • 타 서비스 운영자의 경우 추가로 구글에 대한 사용자의 민감 정보를 별도로 신경써서 관리를 해야한다.

해당 방식은 매우 위험하고 서비스 운영자(구글), 타 서비스 운영자(유튜브 분석기), 사용자 모두에게 불편한 방식이다. 이러한 문제를 해결하고자 OAuth 라는 프로토콜이 등장하게 된다. 

OAuth (Open Authorization)

앞서 우리는 사용자의 자원을 제 3 서비스에게 제공하기 위해 서비스의 "로그인 민감 정보"를 제공해야했다. 이 과정에서 다양한 문제가 발생할 수 있음을 알 수 있었다. OAuth 는 바로 이런 문제를 해결하기 위해서 등장했다.

역사

OAuth 는 사실 De facto 표준(사실상 표준) 으로 기관들이 모여 "이게 표준이야" 라고 모여서 만들어진 표준은 아니며 널리 사용되여 API 접근 권한에 대한 사실상의 표준이 되었다.

2006년 트위터에서 OpenID 구현과 동시에 위 예시와 마찬가지로 Twitter, Google 과 같은 서비스에 접근하여 특정 정보를 획득하고자 하는 수요가 발생하였고, 이러한 API 에 접근하기 위한 표준이 없다는 사실에 OAuth Discussion Group 이 탄생하여 2007년 OAuth 1.0 에 대한 최종 초안이 등장하였다.

정의

OAuth 2.0 의 RFC의 요약부터 살펴보자.

  • OAuth 2.0 인증 프레임워크는 Resource OwnerHTTP Service 간 승인 상호 작용을 조율하여 Resource Owner 를 대신하여 타사 애플리케이션(Client)이 HTTP Service 에대한 제한된 액세스 권한을 얻거나, 타사 애플리케이션이 자체적으로 액세스 권한을 얻을 수 있도록 허용한다.
    • 잘 살펴보면 "인가(Auhtorization)" 에 대해서 이야기하고 있다. "인증"이 아님을 잘 생각해두자.

OAuth 프로토콜은 결국 다음과 같은 목적을 위해서 만들어졌다고 요약해볼 수 있다.

  • 타사 어플리케이션이 자원 소유자의 자원에 대해서 "제한적 접근" 을 할 수 있도록 "인가"를 제공하며, 이 과정이 "안전하게" 이루어 질 수 있도록 하는 프로토콜

그런데, 여기서 등장하는 "역할"에 관한 용어들이 우리에게 친숙한 client-server 구조와 다소 다르다. 그래서 먼저 각 역할에 대한 OAuth 에서의 재정의를 살펴보자.

Role

Resource Owner

  • 최종 사용자를 일컫는다. Google 에 "자원"을 소유한 본인을 일컫기에 Resource Owner 라는 명칭이 붙는다.
  • Resource Owner 는 결과적으로 Client 에게 자원에 대한 접근에 대한 권한을 위임하게 된다.

Resource Server

  • 예시에서 Google 에 해당한다. Resource Owner 가 소유한 자원을 가지고있는 서버를 일컫는다.
  • 해당 서버는 위임된 권한을 인증하는 토큰을 검사하고, 해당 자원에 대한 응답을 보내게 된다.

Client

  • 예시에서 유튜브 분석기 에 해당한다. Resource Owner 가 소유한 자원에 대한 권한을 위임 받아, 대리로 자원에 대해 다루는 주체이다.
  • 일반적으로 Resource Owner 가 특정 서비스를 활용하기 위해 접근하는 서버가 그 대상이 된다.
  • Client 의 경우 confidential / public 두 종류로 나눌 수 있다.
    • confidential client 의 경우 일반적으로 자격 증명의 기밀성을 유지할 수 있는 client를 말한다. 일반적으로 web server 를 통해서 접근 권한을 획득하는 경우 이에 해당한다.
      • confidential 한 경우에는 public / private key pair 와 같이 client - authorization server 간의 인증 방법을 제공하곤한다.
    • public client 의 경우 native app / user-agent 를 끼고(브라우저 위에서) 동작하는 js app 과 같이 resource owner 가 직접 실행하여 자격 증명에 대한 기밀성을 유지할 수 없는 client 를 말한다.
  • 각 종류에 따라 Grant를 받는 방식의 변화가 발생할 수 있다.
    • web server 의 경우에는 보통 authorization_code를 통한다.
    • native app / browser app 과 같은 경우에는 implict 를 사용하여 문제를 해결할 수 있다.
  • + Authorization server 에서는 각 client 에 대한 식별자를 제공해야한다. 이는 Authorization server 내에서 구분되며, secret 으로 활용하는 내용은 아니다.
    • CLIENT_ID 와 같은 key 값을 제공하는데 이게 보통 여기에 해당한다.

Authorization Server

  • 앞선 예시에서는 Google OAuth Server 가 해당이 된다. 해당 자원에 대한 인가 권한을 발급하기 위한 서버로 Resource Owner 으로부터 권한을 승인받고 Resource Owner 에게 Access Token 을 실제로 발급하는 주체가 된다.
    • 이후 해당 Access Token을 Resource OwnerClient 에게 제공하여 권한을 위임할 것이다''.

Grant

role 부분에서 잠시 Grant에 대한 내용이 있었다. Grant 는 "어떤 방식으로 인가를 받을 것인가?" 라는 것에 대한 내용이다.

  1. Authorization Code Grant: 가장 보편적인 방식으로, 사용자가 Authorization Server에서 인증한 후 Authorization Code를 client 에게 전달한다
  2. Implicit Grant: 주로 브라우저 기반 애플리케이션에서 사용되며, Access Token을 직접받는 간단한 방식
  3. Resource Owner Password Credentials Grant: 사용자가 자신의 사용자 이름과 비밀번호를 직접 클라이언트에 입력하여 Access Token을 받는 방식
  4. Client Credentials Grant: 클라이언트가 인증서와 같은 자신의 자격증명을 사용하여 Access Token을 받을 때 사용한다

Protocol Endpoint

Authorization Endpoint

Authorization endpoint 는 resource owner 와 상호작용을하고, 인가에 대한 grant 를 받기 위해서 사용한다. authorization server 는 반드시 resource owner 에 대한 증명을 해야한다.(구글 로그인을 할 때 아이디, 패스워드를 입력하는 부분) 해당 과정은 인증 과정과 관련이 되어있어 민감 정보를 다루기도 하기에 TLS 를 통해서 이루어져야한다.

  • response_type
    • authorization endpoint 는 어떤 형태의 grant를 사용할 지에 대해서 명시를 해야한다. 이를 위한 부분이 required parameter 이다. 일반적으로 OAuth 소셜 로그인 과정에서는 "code" 를 사용하는데, 이는 authorization code 형태의 grant를 수행함을 나타낸다. 반면, access token 을 직접 받는 implicit grant 의 경우 "token"을 사용한다.

Redirection Endpoint

  • Resource owner 와의 상호작용을 종료하고 난 후, resource owner 의 user agent 를 사용하여 client 에게 보낼 방법이 필요하다. 이 때 사용하는 endpoint 가 바로 redirection endpoint 이다.
    • 소셜 로그인 과정에서 redirect uri 를 설정하곤하는데 해당 부분이 이 부분이다.
  • 이 또한 나름의 민감 정보인 authorization code 를 전달하기에 TLS 를 통한 통신을 권장하고 있다.

Token Endpoint

  • Client 가 authorization code 혹은 refresh token 을 제시하고 access token 을 얻는데 사용하는 authoirzation server 단의 endpoint 이다. 이 때 POST method를 통해 요청해야한다.

OAuth 2.0 프로토콜의 흐름

RFC 6749 예시

     +--------+                               +---------------+
     |        |--(A)- Authorization Request ->|   Resource    |
     |        |                               |     Owner     |
     |        |<-(B)-- Authorization Grant ---|               |
     |        |                               +---------------+
     |        |
     |        |                               +---------------+
     |        |--(C)-- Authorization Grant -->| Authorization |
     | Client |                               |     Server    |
     |        |<-(D)----- Access Token -------|               |
     |        |                               +---------------+
     |        |
     |        |                               +---------------+
     |        |--(E)----- Access Token ------>|    Resource   |
     |        |                               |     Server    |
     |        |<-(F)--- Protected Resource ---|               |
     +--------+                               +---------------+

                     Figure 1: Abstract Protocol Flow
  1. (A) Client 는 Resource Owner 에게 인증 요청을 한다. (구글로 로그인하기 버튼 클릭)
  2. (B) Resource Owner 는 권한 부여 요청을 받고, 권한에 대한 허가를 내준다.
    1. 다소 실제보다 짧게 표현되는데 구글 로그인을 하고 권한(Scope)을 설정하는 부분이 이에 해당한다.
    2. (A) 과정에서 Client 는 구글 Authorization Server 로의 경로로 사용자를 보내고, (B) 과정에서 로그인 -> 인증 정보 획득 -> Authorization code을 Client 에게 전송을 말하고 있다.
  3. (C) Client 는 Authorization 서버로 가서 해당 권한으로 Access Token 을 발급 받는다.
    1. Client 는 2번 과정에서 Authorization code 를 이미 획득하였고, Authorization code 를 통해 Access Token 을 발급받는 과정.
  4. (D) Authorization Server 는 Authorization code 를 검증하고 권한에 대한 Access Token 을 발급한다.
    1. 여기서 받은 Access token 을 통해 앞으로 Client 는 Resource Owner 의 자원에 대해서 대리로 접근할 수 있다.
  5. (E) Client 는 Access Token 을 들고 Resource Owner 에게 해당 자원에 대해서 요청을 한다.
  6. (F) Resource Server 는 검증된 Access token 일 경우 해당 자원 요청에 대해서 응답한다.
  • 아래 hudi.blog 에서 제공한 예시는 해당 과정을 더 자세히 표현하고 있다.
OAuth 2.0 동작과정 시퀀스 다이어그램
hudi.blog 에서의 조금 더 자세한 예시-
  • 여기서 조금 다른 점은 DB 에 Access Token 저장 부분이다.
    • 일반적으로 어플리케이션에서 소셜 로그인을 할 경우 "회원 이메일" " 식별정보" "성별" 등을 획득하곤한다.
    • 해당 정보는 (거의) 불변성을 가지고 있다. 첫 로그인에서 access token 을 통해서 획득하고 더 이상 resource server 에 요청하지 않아도 되는 정보이기도 한다.
    • 따라서, Application 을 작성하기에 따라 달라지게 된다. 만약 유저가 "성별" "나이" "이메일" 등의 요소를 요청할 경우 Resource server 로 부터 접근하여 해당 정보를 제공할 수도 있고, Application 의 DB에 해당 정보를 미리 저장할 수도 있다.
    • 만약 Youtube Analyzer 와 같은 어플리케이션이라면 access token 의 소유는 필연적일 것이다. (계속 들고와서 보여줘야하니까..)

OIDC

OAuth 프로토콜의 경우 제한된 엑세스에 대해 인가를 수행하는 프로토콜이다. 즉, "인증"과는 다소 멀다. 소셜 로그인 과정을 떠올려보면 이를 알 수 있다.

  • Authorization code 를 받아오고, 이를 바탕으로 access token 요청
  • access token을 바탕으로 회원의 정보에 대한 요청

access token 만을 획득하는 과정에서 사실 "인증"의 부분은 끝난 것이나 다름없다. 그러나, 회원에 대한 필요한 정보 (email / id) 등을 획득하기 위해서 access token 을 사용하여 추가로 정보를 요청해야하는 불편함이 있었다.

이러한 문제를 해결하고자하는 것이 OIDC (OpenID Connect) 이다. 원리는 간단하다. OAuth 2.0 프로토콜 위에서 access token 을 돌려주는데, 해당 토큰을 JWT 로 구성하고 payload 에 필요한 회원의 정보를 같이 전달하는 것이다. 이를 통해 소셜 로그인과 같이 회원에 대한 인증 작업을 수행하기 위해 2번의 요청을 한 번으로 줄일 수 있다.

현재 네이버의 경우에는 OIDC 를 지원하지 않으며, 카카오의 경우에는 OIDC 를 지원하고 있다. 위에서 배운 내용을 토대로 아래 카카오에서 제시한 로그인 흐름을 보면 조금 더 이해가 잘될 것이다.

카카오 로그인 과정
kakao 소셜 로그인 흐름

궁금할만한 정보들

Authorization code / Access code 를 나누는 이유

  • Resource Owner 가 Client 에게 인가 정보를 전송하는 방법은 URI를 통한 방법밖에 없다. (Redirect URI 경로를 따라가면서 전송하기 때문)
    • 이 과정에서 바로 브라우저로 노출되는데, Access Token 은 해당 resource owner 의 자원을 요청할 수 있는 민감한 토큰이다.
    • 따라서 access token 을 받는 주체를 server로 변경한다. 이 때 단순하게 authorization code 만을 들고 가는 것이 아닌, Authentication server 와 미리 합의된 client id / secret 과 같은 정보를 함께 전송한다.
    • 그 결과, 보다 더 안전하게 resource owner 의 자원에 접근할 수 있다.

Refresh token 의 필요성

  • Access token 은 stateless 하다. stateless 한 access token 이 노출되거나 탈취당할 경우 해당 access token 으로 접근하는 것을 방법이 마땅히 존재하지 않는다. (JWT 에서의 문제와 동일)
  • 따라서, access token 의 expire 주기를 짧게 주고, refresh token 을 통해서 access token 만료시 빠르게 refresh token 을 통해 access token 을 갱신하는 용도로 활용한다.

Authorization vs Authentication

  • 정말 단순하게 실사용 예시로 표현하면 다음과 같다.
    • Authentication (인증)
      • 로그인 / OTP
    • Authorization (인가)
      • Cookie / JWT / API Key
  • Authentication (인증) 은 사용자의 신원을 확인하는 행위이다. 따라서, 이미 저장된 데이터와 비교하면서 "해당 사용자가 맞는가?"를 검증한다.
    • ID / PW 로그인
    • 2FA (OTP / Email 추가 인증 / 생체 인증 ...)
    • API 인증
      • 위 OAuth 에서 API_ID / API_SECRET 을 통해서 신원을 확인하는 작업이 있는데 인증의 과정이라고 볼 수 있음.
  • Authorization (인가)은 "권한" 에 대한 이야기이다. "어떤 자원에 누가 무엇을 할 수 있는가?" 를 검증하는 행위이다.
    • JWT
    • Session + Cookie
    • OAuth
    • OpenID
      • 위 OIDC 때문에 조금 헷갈릴 수 있다. 다만 근본적으로 생각해보면 OAuth 2.0 위에서 Authentication (인증 서버에서의 인증) + Authorization (토큰을 통한 접근) 모두 수행하는 것을 알 수 있음. (JWT 생각해보면 이해가 빠르다.)

참고 자료

OAuth 2.0 — OAuth
RFC 6749: The OAuth 2.0 Authorization Framework
The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its own behalf. This specification replaces and obsoletes the OAuth 1.0 protocol described in RFC 5849. [STANDARDS-TRACK]
OAuth 2.0 개념과 동작원리
OAuth 2.0 / Refresh token, Authorization code
평소 OAuth 2.0에서 궁금했던것들. Refresh token이 왜 필요한건지, Authorization code Grant flow에서 왜 굳이 Access token 대신 Authorization code를 중간에 거치는건지에 대한 고민 같은것들을 정리해봤다.
OAuth - Wikipedia
비슷해보이지만 다른 두 친구를 소개합니다. Authentication vs Authorization | 아웃풋 트레이닝
내외부 관련된 서비스를 런칭하거나 기능을 추가할때 같이 논의되는 Auth에 대해 들어보셨을텐데요, 보통 Auth라고 하면 Authentication을 말하거나 Authentication + Authorization를 통칭합니다. 이렇게 구분지어 분리를 해놓은 거 보니 다른 것 같긴한데 과연 무엇이 어떻게 다를까요? 함께 알아가요.