인가 코드 플로

인가 코드 플로 (Authorization Code Flow)

"사용자를 로그인시키고 싶은데, 토큰을 브라우저에 노출시키고 싶지 않을 때" 어떤 흐름을 써야 할까요? 정답은 인가 코드 플로예요. 이 방식은 모든 토큰을 **토큰 엔드포인트(Token Endpoint)**에서만 돌려주어, 브라우저(사용자 에이전트)에는 승인 코드만 오가게 해요. 그래서 사용자 에이전트에 접근할 수 있는 악성 앱에 토큰이 노출될 일이 적어요. 여기서는 그 흐름이 어떻게 진행되고, 어떤 파라미터를 주고받는지 살펴볼게요.

출처: OpenID Connect Core 1.0 — 3.1. 인가 코드 플로

본문

흐름의 단계

인가 코드 플로는 다음 순서로 진행돼요.

  1. 클라이언트가 원하는 요청 파라미터를 담아 **인증 요청(Authentication Request)**을 준비해요.
  2. 클라이언트가 그 요청을 인가 서버(Authorization Server)로 보내요.
  3. 인가 서버가 최종 사용자를 인증해요.
  4. 인가 서버가 최종 사용자의 동의(Consent/Authorization)를 얻어요.
  5. 인가 서버가 **승인 코드(Authorization Code)**를 들고 사용자를 클라이언트로 되돌려보내요.
  6. 클라이언트가 승인 코드를 토큰 엔드포인트에서 응답으로 교환해요.
  7. 클라이언트가 ID 토큰과 액세스 토큰을 담은 응답을 받아요.
  8. 클라이언트가 ID 토큰을 검증하고 사용자의 주체 식별자(subject identifier)를 얻어요.

인가 서버는 승인 코드를 액세스 토큰으로 바꾸기 전에 클라이언트를 인증할 수도 있어요. 그래서 이 흐름은 클라이언트가 인가 서버와 클라이언트 시크릿(Client Secret)을 안전하게 보관할 수 있는 경우에 적합해요.

인가 엔드포인트(Authorization Endpoint) 요청

인증 요청은 OAuth 2.0 인가 요청에 인증을 더한 형태예요. 인가 엔드포인트와의 통신은 반드시 TLS를 사용해야 해요. 인가 서버는 인가 엔드포인트에서 HTTP GET과 POST를 모두 지원해야 해요.

인가 코드 플로에서 쓰는 OAuth 2.0 요청 파라미터는 다음과 같아요.

  • scope (REQUIRED) — OpenID Connect 요청은 반드시 openid 스코프 값을 포함해야 해요. openid가 없으면 동작이 아예 정의되지 않으니 주의해요. 다른 스코프 값은 함께 있어도 되고, 이해하지 못하는 스코프 값은 무시하길 권장해요.
  • response_type (REQUIRED) — 인가 코드 플로에서는 code 값을 써요.
  • client_id (REQUIRED) — 인가 서버에서 유효한 OAuth 2.0 클라이언트 식별자예요.
  • redirect_uri (REQUIRED) — 응답을 보낼 리다이렉션 URI. OP에 미리 등록된 리다이렉션 URI 값 중 하나와 정확히 일치해야 해요. https 스킴 사용을 권장하며, confidential 클라이언트면 http도 쓸 수 있어요.
  • state (RECOMMENDED) — 요청과 콜백 사이의 상태를 유지하는 불투명 값. 보통 CSRF(사이트 간 요청 위조) 방지를 위해 브라우저 쿠키와 암호학적으로 묶는 데 써요.
  • response_mode (OPTIONAL) — 인가 엔드포인트에서 파라미터를 돌려주는 방식을 알려주는 값.

이 스펙이 추가로 정의하는 요청 파라미터도 있어요.

  • nonce (OPTIONAL) — 클라이언트 세션을 ID 토큰과 연결하고 재생 공격을 막는 문자열. 값은 추측이 불가능할 만큼의 충분한 엔트로피를 가져야 해요.
  • display (OPTIONAL) — 인가 서버가 인증·동의 UI를 어떻게 표시할지 지정. 값은 page, popup, touch, wap가 정의돼 있어요.
  • prompt (OPTIONAL) — 인가 서버와의 상호작용 방식을 지정. login이면 이미 로그인돼 있어도 다시 인증하고, none이면 아직 인증되지 않은 경우 오류를 돌려줘요.

성공적인 인증 응답

인가 코드 플로에서 인증 응답은 OAuth 2.0의 응답 파라미터를 redirect_uri에 쿼리 파라미터로 붙여 보내요. 실제 예시는 다음과 같아요.

HTTP/1.1 302 Found
Location: https://client.example.org/cb?
code=SplxlOBeZQQYbYS6WxSbIA
&state=af0ifjsldkj

토큰 엔드포인트(Token Endpoint)

액세스 토큰, ID 토큰, 필요하면 리프레시 토큰을 얻으려면 클라이언트가 토큰 요청을 토큰 엔드포인트로 보내요. 토큰 엔드포인트와의 통신도 반드시 TLS를 사용해야 해요.

토큰 요청은 grant_type 값으로 authorization_code를 쓰고, confidential 클라이언트라면 자신의 client_id에 등록된 인증 방식으로 토큰 엔드포인트에 인증해야 해요. 파라미터는 HTTP POST와 폼 직렬화(Form Serialization)로 보내요.

POST /token HTTP/1.1
Host: server.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaG...M2JW

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient.example.org%2Fcb

인가 서버는 토큰 요청을 검증할 때 클라이언트를 인증하고, 승인 코드가 인증된 클라이언트에게 발급된 것인지·유효한지·이미 쓰였는지 확인해요. 그리고 토큰 요청의 redirect_uri가 최초 인증 요청에 들어 있던 값과 같은지도 확인해요.

성공적인 토큰 응답

성공 응답은 application/json 미디어 타입으로, OAuth 2.0 응답 파라미터에 id_token이 추가로 포함돼요. token_type 값은 기본적으로 Bearer예요. 토큰·시크릿 등 민감한 정보가 담긴 응답에는 반드시 Cache-Control: no-store 헤더를 넣어야 해요.

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{
  "access_token": "SlAV32hkKG",
  "token_type": "Bearer",
  "refresh_token": "8xLOxBtZp8",
  "expires_in": 3600,
  "id_token": "eyJhbG...mlzcyI6..."
}

요청이 유효하지 않거나 권한이 없으면 인가 서버는 400 응답과 application/json 본문으로 오류를 돌려줘요.

ID 토큰의 추가 클레임과 검증

인가 코드 플로에서는 ID 토큰에 추가로 at_hash 클레임이 들어갈 수 있어요. 이 값은 access_token 값의 해시에서 왼쪽 절반을 base64url로 인코딩한 것으로, ID 토큰의 JOSE 헤더에 쓰인 해시 알고리즘으로 계산해요. 예를 들어 algRS256이면 액세스 토큰을 SHA-256으로 해시한 뒤 왼쪽 128비트를 base64url로 인코딩해요.

클라이언트는 토큰 응답의 ID 토큰을 받으면 2장의 ID 토큰 검증 규칙과 추가 규칙을 따라 검증해야 해요.

더 알아보기

  • ID 토큰 자체의 클레임 구조는 ID 토큰 (ID Token) 문서를 참고해요.
  • 승인 코드가 OAuth 2.0에서 다뤄지는 원리(RFC 6749 4.1절)는 OAuth 2.0 인가 코드 플로를 함께 보면 좋아요.
  • 사용자 정보 조회는 코어 스펙의 UserInfo 엔드포인트 설명을 참고해요.