OAuth 2.0 grant type은 왜 네 가지로 나뉘었는가

seonwoo_jung·3일 전

1. 도입

OAuth 2.0을 처음 보면 authorization_code, client_credentials, password, implicit 같은 이름이 먼저 나온다. 문제는 이 값들이 단순한 옵션처럼 보인다는 점이다. 로그인 화면이 있으면 authorization code를 쓰고, 서버끼리 호출하면 client credentials를 쓴다는 식으로 외우면 당장은 지나갈 수 있다. 하지만 실제 인증 서버를 붙이거나 보안 검토를 하다 보면 "왜 이 흐름이어야 하는가"를 설명해야 하는 순간이 온다.

RFC 6749는 이 값을 "authorization grant"라고 부른다. 리소스 소유자가 클라이언트에게 권한을 위임했다는 사실을 표현하는 증거이고, 클라이언트는 이 grant를 토큰 엔드포인트에 제출해서 access token을 받는다. 그래서 grant type은 로그인 방식의 이름이라기보다 권한 위임 증거를 어떤 형태로 전달할지 정하는 선택지에 가깝다.

grant type을 고르는 일은 "누가 권한을 주는가", "클라이언트를 믿을 수 있는가", "토큰이 어디를 지나가도 되는가"를 고르는 일이다.

이 글은 RFC 6749의 네 가지 grant type을 기준으로 각각이 어떤 상황을 해결하려고 만들어졌는지 정리한 글이다. 최신 OAuth 보안 권고에서는 일부 흐름의 사용을 제한하거나 다른 방식으로 대체하라고 권고하지만, 여기서는 먼저 RFC 6749의 원래 모델을 기준으로 흐름을 이해한다.

2. grant type은 access token으로 가는 중간 증거다

OAuth 2.0에는 네 가지 역할이 있다. resource owner는 보호된 자원의 주인이고, resource server는 API 서버다. client는 API를 호출하려는 애플리케이션이며, authorization server는 권한 승인과 토큰 발급을 맡는다. 보통 우리가 만드는 백엔드 API는 resource server가 되고, 로그인/인가 서버는 authorization server가 된다.

RFC 6749 §1.2의 기본 흐름을 단순화하면 다음과 같다.

Resource Owner
      |
      | 1. 권한 부여
      v
Client ---- 2. authorization grant ----> Authorization Server
Client <--- 3. access token ------------ Authorization Server
Client ---- 4. access token -----------> Resource Server

여기서 grant는 access token 자체가 아니다. 토큰을 받기 위해 제출하는 재료다. authorization code grant에서는 짧게 사는 code가 grant가 되고, client credentials grant에서는 클라이언트 자신의 인증 정보가 grant가 된다. resource owner password credentials grant에서는 사용자의 아이디/비밀번호가 직접 grant로 쓰이고, implicit grant에서는 토큰이 프론트 채널로 바로 돌아온다.

이 차이는 보안 경계와 연결된다. 브라우저를 거치는 값은 URL 히스토리, 리다이렉트, 스크립트 환경에 노출될 가능성이 있다. 반대로 백엔드 서버끼리 직접 통신하는 back channel은 상대적으로 통제하기 쉽다. OAuth의 여러 grant type은 이 경계를 다르게 놓는다.

3. 네 가지 grant type은 무엇을 맞바꾸는가

RFC 6749 §1.3은 네 가지 기본 grant type을 소개한다. 이름만 보면 비슷하지만, 실제로는 토큰이 지나가는 경로와 신뢰 모델이 다르다.

grant type핵심 아이디어주로 어울리는 상황주의할 점
Authorization Code브라우저로는 code만 받고, 토큰은 서버 간 요청으로 교환서버가 있는 웹 앱redirect URI와 code 탈취 방어가 중요
Implicitauthorization endpoint에서 access token을 바로 반환과거의 브라우저 기반 앱토큰이 프론트 채널에 노출되기 쉽다
Resource Owner Password Credentials사용자의 비밀번호를 클라이언트가 받아 토큰으로 교환높은 신뢰 관계의 레거시/특수 상황클라이언트가 비밀번호를 직접 보게 된다
Client Credentials사용자 없이 클라이언트 자신의 권한으로 토큰 발급서버 간 호출, 배치, 내부 API사용자 위임이 아니라 애플리케이션 권한이다

Authorization Code grant는 가장 대표적인 흐름이다. 사용자는 authorization server에서 로그인하고 동의한다. authorization server는 클라이언트의 redirect URI로 authorization code를 돌려준다. 클라이언트는 이 code와 자신의 인증 정보를 토큰 엔드포인트에 보내 access token을 받는다. 중요한 점은 access token이 브라우저 리다이렉트 결과로 직접 노출되지 않는다는 것이다.

Implicit grant는 이 중간 code 교환을 생략한다. RFC 6749 당시에는 브라우저에서 실행되는 클라이언트가 client secret을 안전하게 보관하기 어렵다는 현실을 반영한 흐름으로 볼 수 있다. 대신 access token이 authorization endpoint 응답에서 바로 나온다. 편한 만큼 노출면이 커지므로, 현재 시스템을 설계할 때는 보안 권고와 플랫폼 가이드를 따로 확인해야 한다.

Resource Owner Password Credentials grant는 이름 그대로 사용자의 자격 증명을 클라이언트가 직접 받는다. authorization server에 로그인 화면을 위임하는 대신, 클라이언트가 username/password를 토큰 엔드포인트로 보낸다. RFC 6749도 이 방식은 resource owner와 client 사이의 신뢰가 높을 때 사용한다고 설명한다. 일반적인 서드파티 앱에는 맞지 않는 흐름으로 이해하는 편이 안전하다.

Client Credentials grant는 사용자 위임이 없다. 클라이언트가 자기 자신으로 인증하고, 자기 권한 범위 안에서 access token을 받는다. 예를 들어 결제 서비스가 정산 배치를 돌리기 위해 내부 정산 API를 호출하는 경우처럼, "사용자 A의 데이터에 접근"이 아니라 "서비스 X가 허용된 API를 호출"하는 모델에 가깝다.

4. Authorization Code와 Client Credentials를 비교해 보면

둘 다 토큰 엔드포인트에 요청한다는 점은 같지만, 요청이 의미하는 권한이 다르다. Authorization Code grant에서는 먼저 resource owner의 승인이 있었다. 따라서 access token은 보통 특정 사용자와 scope에 묶인다. Client Credentials grant에서는 사용자가 등장하지 않는다. 토큰의 주체는 클라이언트 애플리케이션이다.

가장 작은 요청 형태를 비교하면 차이가 더 잘 보인다.

POST /token HTTP/1.1
Host: auth.example.com
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=SplxlOBeZQQYbYS6WxSbIA&
redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback

위 요청에서 핵심은 code다. 이 code는 앞 단계에서 사용자 승인과 redirect URI 검증을 거쳐 발급된 값이다. 클라이언트 인증 정보는 "이 code를 교환하러 온 클라이언트가 맞는가"를 확인하는 데 쓰인다.

POST /token HTTP/1.1
Host: auth.example.com
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&
scope=settlement:read

반면 client credentials 요청에는 사용자 승인 code가 없다. 클라이언트가 자기 인증 정보로 바로 토큰을 요청한다. 그래서 이 토큰으로 사용자별 마이페이지 API를 호출하게 만들면 모델이 어긋난다. "이 토큰은 누구의 위임인가?"라는 질문에 답할 사용자가 없기 때문이다.

실무에서 헷갈리는 지점은 "백엔드가 있으니 무조건 client credentials"가 아니라는 점이다. 사용자가 로그인해서 자신의 캘린더, 주문, 문서를 보게 하는 서비스라면 백엔드가 있더라도 authorization code 흐름이 자연스럽다. 백엔드는 토큰을 안전하게 교환하고 보관하는 역할을 맡을 뿐, 권한의 출처는 여전히 사용자 승인이다.

5. 선택 기준을 질문으로 바꿔 보기

grant type을 표로 외우기보다 다음 질문을 순서대로 던지는 쪽이 더 오래 남았다.

첫째, 사용자가 직접 권한을 위임하는가? 그렇다면 authorization code 계열을 먼저 떠올린다. 사용자가 없다면 client credentials가 후보가 된다. 둘째, 클라이언트가 secret을 안전하게 보관할 수 있는가? 서버 사이드 웹 앱은 가능하지만, 브라우저나 모바일 앱은 그렇지 않다고 보는 편이 일반적이다. 셋째, access token이 브라우저 리다이렉트 경로에 직접 노출되어도 되는가? 이 질문에 자신 있게 "그렇다"고 답하기 어렵다면 implicit 흐름은 피하는 쪽으로 검토한다.

ROPC는 더 엄격하게 봐야 한다. 사용자의 비밀번호가 클라이언트 애플리케이션을 지나간다는 사실만으로도 부담이 크다. 조직 내부의 오래된 신뢰 관계, 마이그레이션, 특수한 1st-party 환경처럼 이유가 분명할 때만 검토하는 흐름으로 보는 것이 낫다.

또 하나 중요한 축은 scope다. RFC 6749 §3.3에 따르면 클라이언트는 access 요청 범위를 scope로 지정할 수 있고, authorization server는 요청 범위를 그대로 받아들이거나 더 좁힐 수 있다. grant type이 "어떻게 토큰을 받을지"를 정한다면, scope는 "받은 토큰으로 어디까지 할 수 있는지"를 제한한다. 둘 중 하나만 맞아도 충분한 것이 아니라, 둘이 함께 맞아야 권한 모델이 설명된다.

6. 정리

OAuth 2.0의 grant type은 인증 화면의 종류가 아니라 access token을 받기 위해 제출하는 권한 증거의 종류다. Authorization Code는 사용자 승인과 서버 간 토큰 교환을 분리하고, Implicit은 과거 브라우저 클라이언트의 제약을 반영해 토큰을 바로 돌려준다. Resource Owner Password Credentials는 사용자의 비밀번호를 직접 다루므로 신뢰 경계가 좁은 상황에만 맞고, Client Credentials는 사용자 없이 애플리케이션 자신의 권한으로 API를 호출할 때 어울린다.

내가 기억하기로는 한 문장으로 충분하다. 사용자 위임이면 authorization code, 서비스 자신이면 client credentials, 비밀번호를 직접 받는 흐름과 토큰을 바로 노출하는 흐름은 특별한 이유가 있을 때만 다시 본다.

다음에 더 파고들 주제는 authorization code에서 redirect URI와 state가 왜 중요한지, 그리고 OAuth 2.0 Security Best Current Practice가 implicit과 ROPC를 어떻게 다루는지다.

참고 자료

  • RFC 6749: The OAuth 2.0 Authorization Framework

0개의 댓글