OAuth 2.0의 주요 컴포넌트와 Authorization Code Grant 흐름

dustle·2026년 5월 25일

1. OAuth 2.0

예를 들어 어떤 사진 인쇄 서비스가 사용자의 구글 포토 사진을 가져와 인쇄하려고 한다고 가정해보겠습니다. 가장 단순한 방법은 사용자에게 구글 ID와 비밀번호를 받아 직접 구글에 로그인하는 것입니다. 하지만 이 방식은 매우 위험합니다.

  • 인쇄 서비스가 사용자의 구글 계정 전체를 마음대로 사용할 수 있습니다.
  • 비밀번호를 변경하면 연동이 끊깁니다.
  • 인쇄 서비스가 해킹되면 구글 비밀번호도 함께 유출됩니다.

OAuth 2.0은 이 문제를 해결하기 위해 비밀번호 대신 권한이 제한된 토큰을 발급하는 방식입니다. 사용자는 비밀번호를 외부 서비스에 노출하지 않고, 필요한 권한만 위임할 수 있습니다.

2. OAuth 2.0의 주요 컴포넌트

OAuth 2.0은 네 가지 역할로 구성됩니다.

Resource Owner (자원 소유자)

자신의 자원에 대한 권한을 가진 주체입니다. 일반적으로 최종 사용자를 의미합니다. 위 예시에서는 구글 포토에 사진을 저장한 사용자입니다.

Client (클라이언트)

Resource Owner의 자원에 접근하려는 애플리케이션입니다. 사진 인쇄 서비스가 여기에 해당합니다. Client는 사전에 Authorization Server에 등록되어야 하며, Client ID와 Client Secret을 발급받습니다.

Authorization Server (인증 서버)

Resource Owner를 인증하고, Client에게 Access Token을 발급하는 서버입니다. 구글의 경우 accounts.google.com이 이 역할을 합니다.

Resource Server (자원 서버)

실제 보호된 자원을 가지고 있는 서버입니다. 유효한 Access Token이 있는 요청에만 응답합니다. 구글 포토 API 서버가 여기에 해당합니다.

Authorization Server와 Resource Server는 같은 시스템일 수도 있고 분리되어 있을 수도 있습니다. 큰 플랫폼은 보통 분리되어 있습니다.

3. 자주 사용되는 용어 정리

Access Token

Resource Server에 접근할 때 사용하는 토큰입니다. 유효기간이 짧은 것이 일반적입니다 (1시간 내외).

Refresh Token

Access Token이 만료되었을 때 새 Access Token을 발급받기 위한 토큰입니다. 유효기간이 길고, 안전한 곳에 저장해야 합니다.

Scope

Client가 요청하는 권한의 범위입니다. 예를 들어 photos.read는 사진 읽기 권한, email은 이메일 조회 권한을 의미합니다. 사용자에게 동의 화면에서 표시되는 항목입니다.

Authorization Code

Authorization Code Grant 방식에서 Access Token으로 교환하기 위해 임시로 사용되는 코드입니다. 짧은 수명(보통 10분 이내)을 가지며 한 번만 사용할 수 있습니다.

Redirect URI

인증 완료 후 사용자를 다시 돌려보낼 Client의 주소입니다. 사전에 Authorization Server에 등록되어야 하며, 정확히 일치하지 않으면 거부됩니다.

OAuth 2.0의 Grant Type 종류

OAuth 2.0은 상황에 따라 여러 인증 흐름(Grant Type)을 제공합니다.

  • Authorization Code Grant: 가장 표준적이고 안전한 방식, 웹 서버 기반 앱에 적합
  • Implicit Grant: 브라우저 기반 앱용이었으나 보안 문제로 현재는 권장되지 않음
  • Resource Owner Password Credentials: 신뢰할 수 있는 1st-party 앱에서만 제한적으로 사용
  • Client Credentials: 사용자 없이 서버 간 통신에서 사용
  • Refresh Token Grant: 기존 Refresh Token으로 새 Access Token을 받을 때 사용

이 중에서 가장 널리 사용되는 Authorization Code Grant를 자세히 살펴보겠습니다.

Authorization Code Grant 흐름

Authorization Code Grant는 백엔드 서버를 가진 웹 애플리케이션에 가장 적합한 방식입니다.
전체 흐름을 시퀀스 다이어그램으로 표현하면 다음과 같습니다.

1단계: 인증 요청

사용자가 Client의 "구글로 로그인" 버튼을 클릭하면, Client는 사용자의 브라우저를 Authorization Server로 리다이렉트합니다. 이때 다음 파라미터를 함께 전달합니다.

  • response_type=code: Authorization Code를 요청한다는 의미
  • client_id: Client 식별자
  • redirect_uri: 인증 후 돌아올 주소
  • scope: 요청하는 권한 범위
  • state: CSRF 방지를 위한 임의의 값

예시 URL:

https://accounts.google.com/o/oauth2/v2/auth?
  response_type=code&
  client_id=CLIENT_ID&
  redirect_uri=https://myapp.com/callback&
  scope=photos.read&
  state=random_string

2단계: 사용자 인증과 동의

Authorization Server는 사용자에게 로그인 화면을 보여줍니다. 이미 로그인되어 있다면 이 단계는 생략됩니다. 그 후 사용자에게 다음과 같은 동의 화면을 보여줍니다.

"Photo Print Service가 사진 보기 권한을 요청합니다. 허용하시겠습니까?"

사용자가 허용하면 다음 단계로 진행됩니다.

3단계: Authorization Code 발급

사용자가 동의하면 Authorization Server는 사용자를 등록된 Redirect URI로 리다이렉트합니다. 이때 URL 쿼리에 Authorization Code와 state 값을 포함합니다.

https://myapp.com/callback?
  code=AUTH_CODE&
  state=random_string

Client는 받은 state 값이 1단계에서 보낸 값과 같은지 검증해야 합니다.

4단계: Access Token 교환

Client는 Authorization Code를 받자마자 자신의 백엔드 서버에서 Authorization Server의 토큰 엔드포인트로 직접 요청을 보냅니다. 이 요청은 브라우저가 아닌 서버 간 통신입니다.

POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=AUTH_CODE&
redirect_uri=https://myapp.com/callback&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET

Authorization Server는 Code와 Client Secret을 검증한 후, Access Token과 Refresh Token을 응답합니다.

{
  "access_token": "ACCESS_TOKEN",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "REFRESH_TOKEN",
  "scope": "photos.read"
}

5단계: 자원 접근

Client는 발급받은 Access Token을 Authorization 헤더에 담아 Resource Server에 요청을 보냅니다.

GET https://photoslibrary.googleapis.com/v1/mediaItems
Authorization: Bearer ACCESS_TOKEN

Resource Server는 토큰을 검증한 후 요청된 자원을 응답합니다.

6단계: Access Token 갱신

Access Token이 만료되면 Client는 Refresh Token을 사용하여 새 Access Token을 발급받습니다.

POST https://oauth2.googleapis.com/token
grant_type=refresh_token&
refresh_token=REFRESH_TOKEN&
client_id=CLIENT_ID&
client_secret=CLIENT_SECRET

왜 Authorization Code를 한 번 더 교환하는가

처음 보면 "Access Token을 바로 주면 되지 왜 Code를 한 번 거치는가?"라는 의문이 듭니다. 이유는 보안 때문입니다.

Authorization Code는 브라우저 URL을 통해 전달되므로 노출 위험이 있습니다. 만약 여기서 바로 Access Token을 전달하면, URL이 브라우저 히스토리, 서버 로그, 리퍼러 헤더 등에 남아 탈취 위험이 큽니다.

반면 Authorization Code는 다음 특성이 있어 안전합니다.

  • 수명이 매우 짧음 (보통 10분)
  • 한 번만 사용 가능
  • Client Secret과 함께 제출해야 Access Token으로 교환됨

즉, Code가 유출되어도 Client Secret이 없으면 Access Token을 받을 수 없습니다.

PKCE 확장

모바일 앱이나 SPA처럼 Client Secret을 안전하게 보관할 수 없는 환경에서는 PKCE(Proof Key for Code Exchange) 확장을 사용합니다.

PKCE는 다음과 같이 동작합니다.

  1. Client가 무작위 문자열 code_verifier를 생성합니다.
  2. code_verifier를 SHA256으로 해시한 code_challenge를 만듭니다.
  3. 인증 요청 시 code_challenge를 함께 보냅니다.
  4. 토큰 교환 시 원본 code_verifier를 보냅니다.
  5. Authorization Server는 code_verifier를 해시한 값이 code_challenge와 일치하는지 확인합니다.

이 방식은 Client Secret 없이도 Authorization Code 탈취 공격을 방어할 수 있습니다. 현재는 모든 Client에 PKCE 적용이 권장됩니다.

보안 고려사항

HTTPS

모든 OAuth 2.0 통신은 반드시 HTTPS로 이루어져야 합니다. HTTP로 전송되는 토큰은 중간자 공격에 무방비입니다.

Redirect URI 검증

Authorization Server는 등록된 Redirect URI와 요청의 Redirect URI가 정확히 일치하는지 확인해야 합니다. 와일드카드나 부분 일치를 허용하면 공격자가 토큰을 가로챌 수 있습니다.

State 파라미터 활용

CSRF 공격을 방지하기 위해 인증 요청마다 임의의 state 값을 생성하고, 콜백에서 같은 값인지 검증해야 합니다.

Client Secret 보호

Client Secret은 절대 브라우저나 모바일 앱에 노출되면 안 됩니다. 백엔드 서버에만 보관하고, 환경 변수나 안전한 시크릿 저장소를 사용합니다.

Scope 최소화

필요한 최소 권한만 요청해야 합니다. 과도한 권한을 요청하면 사용자가 거부하거나, 탈취 시 피해가 커집니다.

토큰 저장 위치

  • Access Token: 메모리에 저장하는 것이 가장 안전합니다.
  • Refresh Token: HttpOnly Secure 쿠키 또는 백엔드 세션에 저장합니다.
  • 로컬 스토리지는 XSS에 취약하므로 권장되지 않습니다.

토큰 폐기 엔드포인트 활용

로그아웃이나 권한 해제 시 Authorization Server의 토큰 폐기 엔드포인트(/revoke)를 호출하여 토큰을 무효화합니다.

마치며

OAuth 2.0은 인증(Authentication)이 아니라 인가(Authorization) 프로토콜입니다. 로그인 자체가 목적이라면 OAuth 2.0 위에 구축된 OpenID Connect를 사용하는 것이 더 적절합니다.

0개의 댓글