Files
document-haness/docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/reference/reference-authorization-code-endpoints.md
T
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.

Follows the import procedure in README.md.

  source/     the originating repository verbatim — 78 documents, 28 SVGs,
              8 manifests, plus .source-revision recording the commit
  final/      the SSOT
    document.md   729 lines written from the 29 experiment documents, not
                  concatenated: what was predicted, what was measured, and
                  where the measurement itself was wrong
    evidence/raw    125 outputs, flattened to <experiment>__<file> because
                    the originals collided (01-baseline.txt appeared three
                    times) and the audit only globs the top level
    evidence/meta   one per raw file; command and exitCode are null and the
                    README says why rather than inventing them
    evidence/browser  22 captures
    assets/       three diagrams through techviz
    .techviz/     their VizSpecs

A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.

Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.

verify-pipeline.py passes. audit-records.py reports no issues.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 22:51:59 +09:00

113 lines
6.9 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"
---
# Authorization Code Flow의 Endpoint와 Credential 이동 기준
Authorization Endpoint에서 Redirect, Token Endpoint, JWK 검증, Resource API까지 각 지점에서 무엇이 이동하고 무엇이 이동하지 않는지 확인한다. 먼저 client_secret이 가는 곳과 가지 않는 곳을 나눈다.
## 관계
- **SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계**
브라우저가 code를 직접 교환하는 흐름에서 endpoint별 이동을 관측했다.
- **Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출**
confidential client가 token endpoint에서 자기 client를 인증하는 실례다.
- **Public Client와 Confidential Client 구분 기준**
client 종류가 정해져야 PKCE와 client 인증의 자리가 정해진다.
## 목적
Authorization Endpoint와 Token Endpoint는 역할과 호출 방식이 다르다.
이 구분을 해야 SPA에서 client_secret이 어디로 갔는지, PKCE가 어느 구간을 지키는지 이해하기 쉽다.
하나는 브라우저의 full-page navigation이고 하나는 server-to-server 호출이 될 수도 있고 browser-to-server 호출이 될 수도 있다.
노출되는 것도, 인증하는 방법도 다르다.
Authorization Endpoint
경로 : 브라우저 주소창 남는 곳 : 히스토리·서버 로그·referrer client 인증 : x
Token Endpoint
경로 : body와 Authorization 헤더 보내는 쪽 : client 종류에 따라 server 또는 브라우저 client 인증 : o
## 규칙
### 1. Authorization Endpoint에는 client_secret을 보내지 않는다
이 요청은 브라우저 주소창을 통해 나간다. 그래서 URL이 주소창에도, 브라우저 히스토리에도, 서버 접근 로그에도, 그리고 링크를 타고 온 경우 referrer에도 남는다. 여기 실리는 값은 client_id, redirect_uri, response_type, scope, state, code_challenge, code_challenge_method다. secret이 필요한 인증은 아직 하지 않는다.
반대로 말하면 이 목록에 없는 값을 여기 넣으면 그 값도 같은 곳에 다 남는다.
### 2. Token Endpoint에서 비로소 client를 인증한다
code를 access token으로 바꾸는 요청은 credential을 URL query가 아니라 body와 Authorization 헤더에 싣는다. 그래서 client 인증을 여기서 한다. confidential client는 client_secret_basic처럼 secret을 함께 보낸다.
주소창과 히스토리에 남지 않는다는 뜻이지 어디에도 기록되지 않는다는 뜻은 아니다. 애플리케이션 debug 로그, reverse proxy 로그, tracing과 APM, packet capture에 남을 수 있어서 credential masking을 따로 둔다.
이 요청을 누가 보내는지는 client 종류에 따라 갈린다. server가 보내면 server-to-server이고, secret이 없는 SPA가 보내면 브라우저가 직접 보낸다. token endpoint를 server 안에서만 부르게 하려면 client 종류부터 confidential로 정해야 한다.
### 3. PKCE는 두 요청을 같은 주체에 묶는다
처음 요청에 code_challenge를 담아 보내고, 교환할 때 원본인 code_verifier를 보낸다.
Authorization Server가 이 둘이 대응하는지 확인하고, 대응해야 토큰 교환이 끝난다.
code를 누가 훔쳐 가도 verifier가 없으면 token으로 바꾸지 못한다.
### 4. issuer 검증값과 JWK 조회 주소를 같은 값으로 맞추려 하지 않는다
issuer는 요청을 보내는 주소가 아니라 token의 canonical issuer identifier다. 검증은 발급된 token의 iss claim이 그 값과 같은지를 본다.
JWK 조회 주소는 실제로 공개키를 가져오는 network 경로다. 이 예제에서는 브라우저가 보는 주소와 컨테이너 안에서 닿는 주소가 다르다. 컨테이너 안에서는 자기 localhost가 그 서버가 아니므로 service 이름을 써야 하고, 브라우저는 그 이름에 닿지 못한다.
issuer 검증값과 endpoint 연결 주소는 따로 구성한다. 둘을 하나로 맞추려 하면 로그인 redirect가 깨지거나 서버가 키를 못 가져온다.
### 5. Resource API는 서명만 보고 끝내지 않는다
서명이 맞다는 것은 그 IdP가 발급했다는 뜻일 뿐이다. 같은 IdP가 다른 API용으로 발급한 token도 서명은 맞다.
그래서 issuer와 유효 시간, 그리고 이 API를 위해 발급됐다는 audience를 함께 본다. audience 검증이 빠지면 옆 서비스의 token으로 우리 API가 열린다.
### 6. redirect_uri는 exact match로 좁힌다
wildcard allowlist는 학습 환경에서 편하다. 다만 허용 범위가 넓으면 같은 호스트의 다른 경로로도 code가 갈 수 있다.
실제로 쓰는 callback 주소만 등록해 두면 code가 도착할 수 있는 곳이 그 하나로 줄어든다.
등록하지 않은 redirect_uri를 보냈을 때 거부하는지 확인하는 검사도 따로 둔다.
### 7. 로그인 구간과 API 호출 구간을 한 줄로 그리지 않는다
로그인 구간은 authorization request에서 시작해 callback과 code 교환을 지나 로그인 상태를 만드는 데까지다. API 호출 구간은 브라우저 입력, 중간 계층의 credential 변환, 보호 자원의 검증, 최종 응답이다.
두 구간을 한 줄로 이어 그리면 누가 code를 바꾸고 누가 API를 부르는지가 겹쳐 보인다. 네 구조가 갈리는 자리가 여기라서 나눠서 그린다.
## 적용 조건
- Authorization Code Flow를 쓰는 client를 설정하거나 문서로 설명할 때
- 브라우저 요청과 server-to-server 요청이 한 흐름에 섞여 있을 때
- endpoint별로 무엇이 노출되는지 나눠야 할 때
- PKCE와 client 인증의 자리를 정할 때
## 예외
- Client Credentials처럼 사용자 없이 token을 받는 흐름은 Authorization Endpoint를 지나지 않는다.
- Device Authorization Grant는 브라우저 redirect 대신 별도의 사용자 code 단계를 쓴다. redirect_uri 항목이 그대로 적용되지 않는다.
## 예시
- authorization request에는 code_challenge_method=S256이 있고 client secret은 없다
- token request에는 code_verifier가 있다. secret을 가진 client는 이 요청에서 자기를 인증한다
- expected issuer는 http://localhost:8080/realms/keycloak-patterns 이고 JWK 조회는 컨테이너 network 주소를 쓴다
- audience에 keycloak-pattern-api 가 없으면 invalid_token 결과가 되어 401이 된다
- redirect allowlist에 wildcard가 있으면 등록한 host의 다른 경로로도 code가 갈 수 있다