114 lines
8.6 KiB
Markdown
114 lines
8.6 KiB
Markdown
---
|
|
id: 39fdf472-82c4-43ed-abec-73de672f08ae
|
|
kind: REFERENCE
|
|
slug: authorization-code-endpoint-credential-movement
|
|
title: Authorization Code Flow의 Endpoint와 Credential 이동 기준
|
|
topic: oauth-oidc-auth-boundary
|
|
topicName: OAuth/OIDC 인증 경계
|
|
project: KeyCloak Patterns
|
|
status: 게시 중
|
|
version: 34
|
|
verifiedOn: 2026-08-25
|
|
studio: "https://hyeonworks.com/studio/documents/39fdf472-82c4-43ed-abec-73de672f08ae/edit"
|
|
public: "https://hyeonworks.com/references/authorization-code-endpoint-credential-movement"
|
|
sourceRevision: keycloak-patterns-lab@2026-08
|
|
source:
|
|
- final/document.md#문제를-어렵게-만든-제약-로그인-흐름과-api-흐름
|
|
- final/document.md#선택이-코드와-흐름에-반영되는-방식-추적-규칙
|
|
---
|
|
|
|
# Authorization Code Flow의 Endpoint와 Credential 이동 기준
|
|
|
|
Authorization Endpoint에서 리다이렉트, Token Endpoint, JWK(JSON Web Key) 검증, Resource API까지 지점마다 무엇이 이동하고 무엇이 이동하지 않는지 확인한다. 먼저 client_secret이 가는 곳과 가지 않는 곳을 나눈다.
|
|
|
|
## 관계
|
|
|
|
- **SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계**
|
|
브라우저가 code를 직접 교환하는 흐름에서 엔드포인트마다 무엇이 오가는지 확인했다.
|
|
- **Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출**
|
|
confidential client가 Token Endpoint를 부를 때 자기 자신을 인증한다.
|
|
- **Public Client와 Confidential Client 구분 기준**
|
|
현재 네 패턴에서 client type, callback/code-exchange ownership, PKCE와 client authentication이 어떻게 배치됐는지 함께 본다.
|
|
|
|
## 목적
|
|
|
|
Authorization Endpoint와 Token Endpoint는 하는 일도 다르고 요청이 나가는 방식도 다르다.
|
|
|
|
하나는 브라우저가 페이지째 넘어가는 full-page navigation이고, 다른 하나는 서버가 보낼 수도 있고 브라우저가 직접 보낼 수도 있는 호출이다.
|
|
|
|
Authorization Endpoint 요청 URL은 브라우저 주소창과 히스토리, Authorization Server의 접근 로그에 남을 수 있다. 다른 문서나 origin으로 이동할 때 referrer에 어느 범위까지 전달되는지는 브라우저의 Referrer-Policy와 이동 대상의 관계에 따라 달라진다. 이 요청에서는 client authentication을 수행하지 않는다.
|
|
Token Endpoint 요청은 값을 요청 본문과 Authorization 헤더에 싣는다. 이 프로젝트에서는 AP1 public SPA가 브라우저에서 이 endpoint를 호출하고 AP2~AP4의 server-side component가 code를 교환한다. 이 호출 위치는 public/confidential client type 자체가 강제하는 것이 아니라 callback과 code exchange를 어느 구성요소가 소유하는지, 그리고 배포 구조를 어떻게 잡았는지에 따라 정해진다.
|
|
|
|
## 규칙
|
|
|
|
### 1. Authorization Endpoint에는 client_secret을 보내지 않는다
|
|
|
|
이 요청은 브라우저 주소창을 통해 나가기 때문에 URL이 주소창과 브라우저 히스토리, Authorization Server 접근 로그에 남을 수 있다. 이후 다른 문서로 이동할 때 referrer에 query까지 전달되는지는 Referrer-Policy와 same-origin/cross-origin 조건에 따라 달라진다. 여기 실리는 값은 client_id, redirect_uri, response_type, scope, state, code_challenge, code_challenge_method다. client authentication용 credential은 이 요청에 싣지 않는다.
|
|
|
|
이 목록에 없는 값을 여기에 실으면 그 값도 같은 곳에 함께 남는다.
|
|
|
|
### 2. confidential client는 Token Endpoint에서 자신을 인증한다
|
|
|
|
code를 액세스 토큰으로 바꾸는 요청은 자격 증명을 URL 쿼리 문자열이 아니라 요청 본문과 Authorization 헤더에 싣기 때문에, client authentication도 이 요청에서 수행할 수 있다. 이 프로젝트의 confidential client들은 `client_secret_basic`을 사용하므로 server-side component가 client secret으로 자신을 인증한다.
|
|
|
|
주소창과 히스토리에 남지 않는다는 뜻이지 어디에도 기록되지 않는다는 뜻은 아니다. 애플리케이션 디버그 로그, 리버스 프록시 로그, 추적 도구와 APM(성능 모니터링 도구), 패킷 캡처에 남을 수 있어서 자격 증명을 가리는 마스킹을 따로 둔다.
|
|
|
|
이 프로젝트에서는 AP1 public SPA가 브라우저에서 token endpoint를 호출하고, AP2~AP4의 confidential component가 server-side에서 code를 교환한다. 다만 public/confidential client 구분 자체가 token endpoint의 호출 위치를 강제하는 것은 아니다. 서버 전용 교환은 callback과 code exchange를 server component가 소유하고 client credential이 browser로 노출되지 않도록 설계함으로써 만든다.
|
|
|
|
### 3. PKCE는 두 요청을 같은 주체에 묶는다
|
|
|
|
로그인을 시작하는 요청에 code_challenge를 담아 보내고, code를 토큰으로 바꿀 때 원본인 code_verifier를 보낸다.
|
|
Authorization Server가 이 둘이 대응하는지 확인하고, 대응할 때만 토큰 교환이 끝난다.
|
|
|
|
그래서 code를 누가 훔쳐 가도 code_verifier가 없으면 토큰으로 바꾸지 못한다.
|
|
|
|
### 4. issuer 검증값과 JWK 조회 주소를 같은 값으로 맞추려 하지 않는다
|
|
|
|
issuer는 요청을 보내는 주소가 아니라 토큰을 발급한 주체를 가리키는 정해진 식별자다. 검증은 발급된 토큰의 iss 클레임이 그 값과 같은지를 본다.
|
|
|
|
JWK 조회 주소는 공개키를 실제로 가져오는 네트워크 경로다. 이 예제에서는 브라우저가 보는 주소와 컨테이너 안에서 닿는 주소가 다르다. 컨테이너 안에서는 자기 localhost가 그 서버가 아니어서 서비스 이름을 써야 하는데, 브라우저는 그 이름에 닿지 못한다.
|
|
|
|
issuer 검증값과 엔드포인트에 연결하는 주소는 따로 구성한다. 둘을 하나로 맞추려 하면 로그인 리다이렉트가 깨지거나 서버가 공개키를 가져오지 못한다.
|
|
|
|
### 5. Resource API는 서명만 보고 끝내지 않는다
|
|
|
|
서명이 맞다는 것은 그 IdP가 발급했다는 뜻일 뿐이다. 같은 IdP가 다른 API용으로 발급한 토큰도 서명은 맞다.
|
|
|
|
그래서 issuer와 유효 시간, 그리고 이 API를 위해 발급됐다는 audience를 함께 본다. audience 검증이 빠지면 옆 서비스에 발급된 토큰으로도 우리 API가 열린다.
|
|
|
|
### 6. redirect_uri는 정확히 일치하는 주소로 좁힌다
|
|
|
|
리다이렉트 허용 목록에 와일드카드를 두면 학습 환경에서는 편하다. 다만 허용 범위가 넓으면 같은 호스트의 다른 경로로도 code가 갈 수 있다.
|
|
|
|
실제로 쓰는 콜백 주소만 등록해 두면 code가 도착할 수 있는 곳이 그 하나로 줄어든다.
|
|
|
|
등록하지 않은 redirect_uri를 보냈을 때 거부하는지 확인하는 검사도 따로 둔다.
|
|
|
|
### 7. 로그인 구간과 API 호출 구간은 따로 따라간다
|
|
|
|
로그인 구간은 authorization request에서 시작해 콜백과 code 교환을 지나 로그인 상태를 만드는 데까지다. API 호출 구간은 브라우저 입력, 중간 계층의 자격 증명 변환, 보호 자원의 검증, 최종 응답이다.
|
|
|
|
로그인 버튼을 누른 뒤 JSON이 돌아올 때까지 code와 토큰, 쿠키가 차례로 바뀐다.
|
|
|
|
두 구간을 한 줄로 이어 그리면 누가 code를 바꾸고 누가 API를 부르는지가 겹쳐 보인다. 네 가지 구조는 여기서 갈리므로 나눠서 그린다.
|
|
|
|
## 적용 조건
|
|
|
|
- Authorization Code Flow를 쓰는 클라이언트를 설정하거나 문서로 설명할 때
|
|
- 브라우저 요청과 서버끼리 주고받는 요청이 한 흐름에 섞여 있을 때
|
|
- 엔드포인트마다 무엇이 드러나는지 나눠야 할 때
|
|
- PKCE와 클라이언트 인증을 어디에 걸지 정할 때
|
|
|
|
## 예외
|
|
|
|
- Client Credentials처럼 사용자 없이 토큰을 받는 흐름은 Authorization Endpoint를 지나지 않는다.
|
|
- Device Authorization Grant는 브라우저 리다이렉트 대신 별도의 사용자 code 단계를 쓰기 때문에 redirect_uri 항목이 그대로 적용되지 않는다.
|
|
|
|
## 예시
|
|
|
|
- authorization request에는 code_challenge_method=S256이 있고 클라이언트 시크릿은 없다
|
|
- token request에는 code_verifier가 있다. 시크릿을 가진 클라이언트는 이 요청에서 자기를 인증한다
|
|
- issuer 검증값은 http://localhost:8080/realms/keycloak-patterns 이고 JWK 조회는 컨테이너 네트워크 주소를 쓴다
|
|
- audience에 keycloak-pattern-api 가 없으면 invalid_token 결과가 되어 401이 된다
|
|
- 리다이렉트 허용 목록에 와일드카드가 있으면 등록한 호스트의 다른 경로로도 code가 갈 수 있다
|