Files
document-haness/.playwright-mcp/page-2026-08-26T11-47-54-042Z.yml
T

455 lines
37 KiB
YAML

- generic [ref=f17e3]:
- link "본문으로 건너뛰기" [ref=f17e4] [cursor=pointer]:
- /url: "#main-content"
- banner [ref=f17e5]:
- generic [ref=f17e6]:
- link "TechLog Studio" [ref=f17e7] [cursor=pointer]:
- /url: /studio
- text: TechLog
- generic [ref=f17e8]: Studio
- navigation "Studio 주 탐색" [ref=f17e10]:
- link "작업본" [ref=f17e11] [cursor=pointer]:
- /url: /studio/documents
- link "게시 기록" [ref=f17e12] [cursor=pointer]:
- /url: /studio/publications
- link "새 문서" [ref=f17e13] [cursor=pointer]:
- /url: /studio/documents/new
- link "주제·프로젝트" [ref=f17e14] [cursor=pointer]:
- /url: /studio/taxonomy
- link "릴리즈" [ref=f17e15] [cursor=pointer]:
- /url: /studio/releases
- link "공개 사이트 보기" [ref=f17e16] [cursor=pointer]:
- /url: /
- button "로그아웃" [ref=f17e17]
- main [ref=f17e18]:
- generic [ref=f17e19]:
- generic [ref=f17e20]:
- region [ref=f17e21]:
- generic [ref=f17e22]:
- paragraph [ref=f17e23]: REFERENCE · VERSION 33
- heading "문서 편집" [level=1] [ref=f17e24]
- paragraph [ref=f17e25]: Authorization Code Flow의 Endpoint와 Credential 이동 기준
- region [ref=f17e26]:
- generic [ref=f17e27]:
- paragraph [ref=f17e28]: DOCUMENT
- heading "기본 정보" [level=2] [ref=f17e29]
- generic [ref=f17e30]:
- generic [ref=f17e31]:
- generic [ref=f17e32]: 제목
- textbox "제목" [ref=f17e33]: Authorization Code Flow의 Endpoint와 Credential 이동 기준
- generic [ref=f17e34]:
- generic [ref=f17e35]: slug
- textbox "slug" [ref=f17e36]:
- /placeholder: 비우면 제목에서 만듭니다 (영문 소문자·숫자·하이픈)
- text: authorization-code-endpoint-credential-movement
- generic [ref=f17e37]:
- generic [ref=f17e38]: 요약
- textbox "요약" [ref=f17e39]: "Authorization Code Flow에서 브라우저와 client, Authorization Server, Resource Server가 주고받는 값을 endpoint별로 정리한다. 특히 `client_secret`과 authorization code, access token이 어느 요청에 포함되는지를 구분한다."
- generic [ref=f17e40]:
- generic [ref=f17e41]: Topic
- combobox "Topic" [ref=f17e42]:
- option "선택하지 않음"
- option "OAuth/OIDC 인증 경계" [selected]
- generic [ref=f17e43]:
- generic [ref=f17e44]: Project
- combobox "Project" [ref=f17e45]:
- option "미지정"
- option "Backend Clean Architecture"
- option "KeyCloak Patterns" [selected]
- option "Liner N + 1문제"
- group "관계" [ref=f17e46]:
- generic [ref=f17e48]:
- generic [ref=f17e49]:
- generic [ref=f17e50]: 관계 1 대상
- combobox "관계 1 대상" [ref=f17e51]:
- option "대상 선택"
- option "인증 구조를 보안 성숙도 단계로 취급하지 않는다"
- option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가"
- option "Authorization Code Flow의 Endpoint와 Credential 이동 기준"
- option "BFF 인증 구조 설계 기준"
- option "BFF가 OAuth Token을 관리하는 조건"
- option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
- option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정"
- option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가"
- option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유"
- option "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건"
- option "외부 IdP Federation을 별도의 인증 구조로 세지 않는다"
- option "외부 IdP Federation과 Application 인증 경계"
- option "OAuth/OIDC 인증 패턴 선택 기준"
- option "OAuth Token과 Application Session을 구분하는 기준"
- option "Public Client와 Confidential Client 구분 기준" [disabled]
- option "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출" [disabled]
- option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
- option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계" [selected]
- generic [ref=f17e52]:
- generic [ref=f17e53]: 관계 1 이유
- textbox "관계 1 이유" [ref=f17e54]: 브라우저가 code를 직접 교환하는 흐름에서 endpoint별 이동을 관측했다.
- generic [ref=f17e55]:
- button "위로" [disabled] [ref=f17e56]
- button "아래로" [ref=f17e57]
- button "삭제" [ref=f17e58]
- generic [ref=f17e59]:
- generic [ref=f17e60]:
- generic [ref=f17e61]: 관계 2 대상
- combobox "관계 2 대상" [ref=f17e62]:
- option "대상 선택"
- option "인증 구조를 보안 성숙도 단계로 취급하지 않는다"
- option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가"
- option "Authorization Code Flow의 Endpoint와 Credential 이동 기준"
- option "BFF 인증 구조 설계 기준"
- option "BFF가 OAuth Token을 관리하는 조건"
- option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
- option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정"
- option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가"
- option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유"
- option "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건"
- option "외부 IdP Federation을 별도의 인증 구조로 세지 않는다"
- option "외부 IdP Federation과 Application 인증 경계"
- option "OAuth/OIDC 인증 패턴 선택 기준"
- option "OAuth Token과 Application Session을 구분하는 기준"
- option "Public Client와 Confidential Client 구분 기준" [disabled]
- option "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출" [selected]
- option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
- option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계" [disabled]
- generic [ref=f17e63]:
- generic [ref=f17e64]: 관계 2 이유
- textbox "관계 2 이유" [ref=f17e65]: confidential client가 token endpoint에서 client 인증을 수행하는 흐름을 보여 준다.
- generic [ref=f17e66]:
- button "위로" [ref=f17e67]
- button "아래로" [ref=f17e68]
- button "삭제" [ref=f17e69]
- generic [ref=f17e70]:
- generic [ref=f17e71]:
- generic [ref=f17e72]: 관계 3 대상
- combobox "관계 3 대상" [ref=f17e73]:
- option "대상 선택"
- option "인증 구조를 보안 성숙도 단계로 취급하지 않는다"
- option "서버 세션 기반 인증 구조는 다중 인스턴스에서 어떻게 운영할 것인가"
- option "Authorization Code Flow의 Endpoint와 Credential 이동 기준"
- option "BFF 인증 구조 설계 기준"
- option "BFF가 OAuth Token을 관리하는 조건"
- option "BFF의 Session과 OAuth2AuthorizedClient를 어디에 저장할 것인가"
- option "Browser Token을 없애면서 BFF에 Session과 CSRF 책임이 생긴 과정"
- option "Forward-Auth 구조에서 Application Authorization을 어디까지 Edge에 둘 것인가"
- option "Forward-Auth에서 Client가 보낸 Identity Header를 신뢰하면 안 되는 이유"
- option "Forward-Auth에서 Identity Header를 신뢰하기 위한 조건"
- option "외부 IdP Federation을 별도의 인증 구조로 세지 않는다"
- option "외부 IdP Federation과 Application 인증 경계"
- option "OAuth/OIDC 인증 패턴 선택 기준"
- option "OAuth Token과 Application Session을 구분하는 기준"
- option "Public Client와 Confidential Client 구분 기준" [selected]
- option "Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출" [disabled]
- option "Refresh Token Rotation과 다중 Replica 경쟁을 어떻게 처리할 것인가"
- option "SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계" [disabled]
- generic [ref=f17e74]:
- generic [ref=f17e75]: 관계 3 이유
- textbox "관계 3 이유" [ref=f17e76]: public/confidential client 구분에 따라 token endpoint의 client 인증 방식이 달라지고, Authorization Code Flow에서는 PKCE 적용 여부도 함께 결정한다.
- generic [ref=f17e77]:
- button "위로" [ref=f17e78]
- button "아래로" [disabled] [ref=f17e79]
- button "삭제" [ref=f17e80]
- button "관계 추가" [ref=f17e81]
- region [ref=f17e82]:
- generic [ref=f17e83]:
- paragraph [ref=f17e84]: REFERENCE
- heading "재사용할 기준" [level=2] [ref=f17e85]
- generic [ref=f17e86]:
- generic [ref=f17e87]: 목적
- textbox "목적" [ref=f17e88]: "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"
- group "규칙" [ref=f17e89]:
- generic [ref=f17e91]:
- generic [ref=f17e92]:
- generic [ref=f17e93]: 규칙 1 제목
- textbox "규칙 1 제목" [ref=f17e94]: Authorization Endpoint에는 client_secret을 보내지 않는다
- generic [ref=f17e95]:
- generic [ref=f17e96]: 규칙 1 본문
- textbox "규칙 1 본문" [ref=f17e97]: Authorization request는 브라우저 navigation으로 전송되므로 URL이 주소창과 브라우저 히스토리, Authorization Server 접근 로그에 기록될 수 있고 이후 navigation에서는 Referrer-Policy 설정에 따라 referrer에도 포함될 수 있다. 여기 실리는 값은 client_id, redirect_uri, response_type, scope, state, code_challenge, code_challenge_method다. secret이 필요한 인증은 아직 하지 않는다. 따라서 authorization request URL에는 노출돼도 되는 값만 포함한다.
- generic [ref=f17e98]:
- button "위로" [disabled] [ref=f17e99]
- button "아래로" [ref=f17e100]
- button "삭제" [ref=f17e101]
- generic [ref=f17e102]:
- generic [ref=f17e103]:
- generic [ref=f17e104]: 규칙 2 제목
- textbox "규칙 2 제목" [ref=f17e105]: Token Endpoint에서 비로소 client를 인증한다
- generic [ref=f17e106]:
- generic [ref=f17e107]: 규칙 2 본문
- textbox "규칙 2 본문" [ref=f17e108]: "token request는 authorization code와 `redirect_uri`, `code_verifier` 등을 request body로 보내고, confidential client는 `client_secret_basic` 같은 방식으로 token endpoint에서 client 인증도 수행한다. 주소창과 히스토리에 남지 않는다는 뜻이지 어디에도 기록되지 않는다는 뜻은 아니다. 애플리케이션 debug 로그, reverse proxy 로그, tracing과 APM, packet capture에 남을 수 있어서 credential masking을 따로 둔다. 이 요청을 누가 보내는지는 client 종류에 따라 갈린다. server가 보내면 server-to-server이고, secret이 없는 SPA가 보내면 브라우저가 직접 보낸다. token endpoint를 server 안에서만 부르게 하려면 client 종류부터 confidential로 정해야 한다."
- generic [ref=f17e109]:
- button "위로" [ref=f17e110]
- button "아래로" [ref=f17e111]
- button "삭제" [ref=f17e112]
- generic [ref=f17e113]:
- generic [ref=f17e114]:
- generic [ref=f17e115]: 규칙 3 제목
- textbox "규칙 3 제목" [ref=f17e116]: PKCE는 두 요청을 같은 주체에 묶는다
- generic [ref=f17e117]:
- generic [ref=f17e118]: 규칙 3 본문
- textbox "규칙 3 본문" [ref=f17e119]: 처음 요청에 code_challenge를 담아서 보내고, 교환할 때 원본인 code_verifier를 보내서 이 두개가 일치하는지 확인 한다. 이 2개가 일치해야 토큰 교환이 되게 된다. code를 누가 훔쳐 가도 verifier가 없으면 token으로 바꾸지 못한다.
- generic [ref=f17e120]:
- button "위로" [ref=f17e121]
- button "아래로" [ref=f17e122]
- button "삭제" [ref=f17e123]
- generic [ref=f17e124]:
- generic [ref=f17e125]:
- generic [ref=f17e126]: 규칙 4 제목
- textbox "규칙 4 제목" [ref=f17e127]: issuer 검증값과 JWK 조회 주소를 같은 값으로 맞추려 하지 않는다
- generic [ref=f17e128]:
- generic [ref=f17e129]: 규칙 4 본문
- textbox "규칙 4 본문" [ref=f17e130]: issuer는 요청을 보내는 주소가 아니라 token의 canonical issuer identifier다. 검증은 발급된 token의 iss claim이 그 값과 같은지를 본다. JWK 조회 주소는 실제로 공개키를 가져오는 network 경로다. 이 예제에서는 브라우저가 보는 주소와 컨테이너 안에서 닿는 주소가 다르다. 컨테이너 안에서는 자기 localhost가 그 서버가 아니므로 service 이름을 써야 하고, 브라우저는 그 이름에 닿지 못한다. issuer 검증값과 endpoint 연결 주소는 따로 구성한다. 둘을 하나로 맞추려 하면 로그인 redirect가 깨지거나 서버가 키를 못 가져온다.
- generic [ref=f17e131]:
- button "위로" [ref=f17e132]
- button "아래로" [ref=f17e133]
- button "삭제" [ref=f17e134]
- generic [ref=f17e135]:
- generic [ref=f17e136]:
- generic [ref=f17e137]: 규칙 5 제목
- textbox "규칙 5 제목" [ref=f17e138]: Resource API는 서명만 보고 끝내지 않는다
- generic [ref=f17e139]:
- generic [ref=f17e140]: 규칙 5 본문
- textbox "규칙 5 본문" [ref=f17e141]: 서명이 맞다는 것은 그 IdP가 발급했다는 뜻일 뿐이다. 같은 IdP가 다른 API용으로 발급한 token도 서명은 맞다. Resource Server는 서명과 함께 issuer, 유효 시간, audience를 검증한다. 특히 audience를 검증해야 다른 resource를 대상으로 발급된 token을 현재 API에서 받아들이지 않는다.
- generic [ref=f17e142]:
- button "위로" [ref=f17e143]
- button "아래로" [ref=f17e144]
- button "삭제" [ref=f17e145]
- generic [ref=f17e146]:
- generic [ref=f17e147]:
- generic [ref=f17e148]: 규칙 6 제목
- textbox "규칙 6 제목" [ref=f17e149]: redirect_uri는 exact match로 좁힌다
- generic [ref=f17e150]:
- generic [ref=f17e151]: 규칙 6 본문
- textbox "규칙 6 본문" [ref=f17e152]: wildcard allowlist는 학습 환경에서 편하다. 다만 허용 범위가 넓으면 같은 호스트의 다른 경로로도 code가 갈 수 있다. 실제로 쓰는 callback 주소만 등록해 두면 code가 도착할 수 있는 곳이 그 하나로 줄어든다. 등록하지 않은 redirect_uri를 보냈을 때 거부하는지 확인하는 검사도 따로 둔다.
- generic [ref=f17e153]:
- button "위로" [ref=f17e154]
- button "아래로" [ref=f17e155]
- button "삭제" [ref=f17e156]
- generic [ref=f17e157]:
- generic [ref=f17e158]:
- generic [ref=f17e159]: 규칙 7 제목
- textbox "규칙 7 제목" [ref=f17e160]: 로그인 구간과 API 호출 구간을 한 줄로 그리지 않는다
- generic [ref=f17e161]:
- generic [ref=f17e162]: 규칙 7 본문
- textbox "규칙 7 본문" [ref=f17e163]: 로그인 구간은 authorization request에서 시작해 callback과 code 교환을 지나 로그인 상태를 만드는 데까지다. API 호출 구간은 브라우저 입력, 중간 계층의 credential 변환, 보호 자원의 검증, 최종 응답이다. 로그인 구간과 애플리케이션 API 호출 구간은 호출 주체가 다를 수 있으므로 별도로 그린다. 그래야 code 교환 주체와 Resource Server 호출 주체를 각각 확인할 수 있다.
- generic [ref=f17e164]:
- button "위로" [ref=f17e165]
- button "아래로" [disabled] [ref=f17e166]
- button "삭제" [ref=f17e167]
- button "규칙 추가" [ref=f17e168]
- group "적용 조건" [ref=f17e169]:
- generic [ref=f17e171]:
- generic [ref=f17e172]:
- generic [ref=f17e173]: 적용 조건 1
- textbox "적용 조건 1" [ref=f17e174]: Authorization Code Flow를 쓰는 client를 설정하거나 문서로 설명할 때
- generic [ref=f17e175]:
- button "위로" [disabled] [ref=f17e176]
- button "아래로" [ref=f17e177]
- button "삭제" [ref=f17e178]
- generic [ref=f17e179]:
- generic [ref=f17e180]:
- generic [ref=f17e181]: 적용 조건 2
- textbox "적용 조건 2" [ref=f17e182]: 브라우저 요청과 server-to-server 요청이 한 흐름에 섞여 있을 때
- generic [ref=f17e183]:
- button "위로" [ref=f17e184]
- button "아래로" [ref=f17e185]
- button "삭제" [ref=f17e186]
- generic [ref=f17e187]:
- generic [ref=f17e188]:
- generic [ref=f17e189]: 적용 조건 3
- textbox "적용 조건 3" [ref=f17e190]: endpoint별로 무엇이 노출되는지 나눠야 할 때
- generic [ref=f17e191]:
- button "위로" [ref=f17e192]
- button "아래로" [ref=f17e193]
- button "삭제" [ref=f17e194]
- generic [ref=f17e195]:
- generic [ref=f17e196]:
- generic [ref=f17e197]: 적용 조건 4
- textbox "적용 조건 4" [ref=f17e198]: PKCE 적용과 client 인증 방식을 정할 때
- generic [ref=f17e199]:
- button "위로" [ref=f17e200]
- button "아래로" [disabled] [ref=f17e201]
- button "삭제" [ref=f17e202]
- button "적용 조건 추가" [ref=f17e203]
- group "예외" [ref=f17e204]:
- generic [ref=f17e206]:
- generic [ref=f17e207]:
- generic [ref=f17e208]: 예외 1
- textbox "예외 1" [ref=f17e209]: Client Credentials처럼 사용자 없이 token을 받는 흐름은 Authorization Endpoint를 지나지 않는다.
- generic [ref=f17e210]:
- button "위로" [disabled] [ref=f17e211]
- button "아래로" [ref=f17e212]
- button "삭제" [ref=f17e213]
- generic [ref=f17e214]:
- generic [ref=f17e215]:
- generic [ref=f17e216]: 예외 2
- textbox "예외 2" [ref=f17e217]: "Device Authorization Grant는 브라우저 redirect가 아니라 device code와 user code를 사용하므로 이 문서의 `redirect_uri` 흐름과는 별도로 본다."
- generic [ref=f17e218]:
- button "위로" [ref=f17e219]
- button "아래로" [disabled] [ref=f17e220]
- button "삭제" [ref=f17e221]
- button "예외 추가" [ref=f17e222]
- group "예시" [ref=f17e223]:
- generic [ref=f17e225]:
- generic [ref=f17e226]:
- generic [ref=f17e227]: 예시 1
- textbox "예시 1" [ref=f17e228]: authorization request에는 code_challenge_method=S256이 있고 client secret은 없다
- generic [ref=f17e229]:
- button "위로" [disabled] [ref=f17e230]
- button "아래로" [ref=f17e231]
- button "삭제" [ref=f17e232]
- generic [ref=f17e233]:
- generic [ref=f17e234]:
- generic [ref=f17e235]: 예시 2
- textbox "예시 2" [ref=f17e236]: token request에는 code_verifier가 있다. secret을 가진 client는 이 요청에서 자기를 인증한다
- generic [ref=f17e237]:
- button "위로" [ref=f17e238]
- button "아래로" [ref=f17e239]
- button "삭제" [ref=f17e240]
- generic [ref=f17e241]:
- generic [ref=f17e242]:
- generic [ref=f17e243]: 예시 3
- textbox "예시 3" [ref=f17e244]: expected issuer는 http://localhost:8080/realms/keycloak-patterns 이고 JWK 조회는 컨테이너 network 주소를 쓴다
- generic [ref=f17e245]:
- button "위로" [ref=f17e246]
- button "아래로" [ref=f17e247]
- button "삭제" [ref=f17e248]
- generic [ref=f17e249]:
- generic [ref=f17e250]:
- generic [ref=f17e251]: 예시 4
- textbox "예시 4" [ref=f17e252]: audience에 keycloak-pattern-api 가 없으면 invalid_token 결과가 되어 401이 된다
- generic [ref=f17e253]:
- button "위로" [ref=f17e254]
- button "아래로" [ref=f17e255]
- button "삭제" [ref=f17e256]
- generic [ref=f17e257]:
- generic [ref=f17e258]:
- generic [ref=f17e259]: 예시 5
- textbox "예시 5" [ref=f17e260]: redirect allowlist에 wildcard가 있으면 등록한 host의 다른 경로로도 code가 갈 수 있다
- generic [ref=f17e261]:
- button "위로" [ref=f17e262]
- button "아래로" [disabled] [ref=f17e263]
- button "삭제" [ref=f17e264]
- button "예시 추가" [ref=f17e265]
- generic [ref=f17e266]:
- generic [ref=f17e267]: 마지막 검증일
- textbox "마지막 검증일" [ref=f17e268]: 2026-08-25
- region [ref=f17e269]:
- generic [ref=f17e270]:
- paragraph [ref=f17e271]: LIVE
- heading "즉시 미리보기" [level=2] [ref=f17e272]
- generic [ref=f17e275]:
- generic [ref=f17e276]:
- navigation "문서 경로" [ref=f17e277]:
- link "Reference" [ref=f17e278] [cursor=pointer]:
- /url: /explore/references
- generic [ref=f17e279]: /
- generic [ref=f17e280]: OAuth/OIDC 인증 경계
- generic [ref=f17e281]: /
- link "KeyCloak Patterns" [ref=f17e282] [cursor=pointer]:
- /url: /projects/keycloak-patterns
- heading "Authorization Code Flow의 Endpoint와 Credential 이동 기준" [level=1] [ref=f17e283]
- paragraph [ref=f17e284]: "Authorization Code Flow에서 브라우저와 client, Authorization Server, Resource Server가 주고받는 값을 endpoint별로 정리한다. 특히 `client_secret`과 authorization code, access token이 어느 요청에 포함되는지를 구분한다."
- generic [ref=f17e285]:
- generic [ref=f17e286]:
- term [ref=f17e287]: 유형
- definition [ref=f17e288]: Reference
- generic [ref=f17e289]:
- term [ref=f17e290]: 프로젝트
- definition [ref=f17e291]: KeyCloak Patterns
- generic [ref=f17e292]:
- term [ref=f17e293]: 게시
- definition [ref=f17e294]: 2026.08.25
- region [ref=f17e295]:
- paragraph [ref=f17e296]: Purpose
- heading "이 기준을 쓰는 이유" [level=2] [ref=f17e297]
- paragraph [ref=f17e298]: "Authorization Endpoint와 Token Endpoint는 역할과 호출 방식이 다르다.이 구분을 해야 SPA에서 client_secret이 어디로 갔는지, PKCE가 어느 구간을 지키는지 이해하기 쉽다.하나는 브라우저의 full-page navigation이고 하나는 server-to-server 호출이 될 수도 있고 browser-to-server 호출이 될 수도 있다.노출되는 것도, 인증하는 방법도 다르다.Authorization Endpoint경로 : 브라우저 주소창 남는 곳 : 히스토리·서버 로그·referrer client 인증 : xToken Endpoint경로 : body와 Authorization 헤더 보내는 쪽 : client 종류에 따라 server 또는 브라우저 client 인증 : o"
- article [ref=f17e299]:
- region [ref=f17e300]:
- heading "판단 기준" [level=2] [ref=f17e301]
- list [ref=f17e302]:
- listitem [ref=f17e303]:
- generic [ref=f17e304]: "01"
- generic [ref=f17e305]:
- heading "Authorization Endpoint에는 client_secret을 보내지 않는다" [level=3] [ref=f17e306]
- paragraph [ref=f17e307]: Authorization request는 브라우저 navigation으로 전송되므로 URL이 주소창과 브라우저 히스토리, Authorization Server 접근 로그에 기록될 수 있고 이후 navigation에서는 Referrer-Policy 설정에 따라 referrer에도 포함될 수 있다. 여기 실리는 값은 client_id, redirect_uri, response_type, scope, state, code_challenge, code_challenge_method다. secret이 필요한 인증은 아직 하지 않는다. 따라서 authorization request URL에는 노출돼도 되는 값만 포함한다.
- listitem [ref=f17e308]:
- generic [ref=f17e309]: "02"
- generic [ref=f17e310]:
- heading "Token Endpoint에서 비로소 client를 인증한다" [level=3] [ref=f17e311]
- paragraph [ref=f17e312]: "token request는 authorization code와 `redirect_uri`, `code_verifier` 등을 request body로 보내고, confidential client는 `client_secret_basic` 같은 방식으로 token endpoint에서 client 인증도 수행한다. 주소창과 히스토리에 남지 않는다는 뜻이지 어디에도 기록되지 않는다는 뜻은 아니다. 애플리케이션 debug 로그, reverse proxy 로그, tracing과 APM, packet capture에 남을 수 있어서 credential masking을 따로 둔다. 이 요청을 누가 보내는지는 client 종류에 따라 갈린다. server가 보내면 server-to-server이고, secret이 없는 SPA가 보내면 브라우저가 직접 보낸다. token endpoint를 server 안에서만 부르게 하려면 client 종류부터 confidential로 정해야 한다."
- listitem [ref=f17e313]:
- generic [ref=f17e314]: "03"
- generic [ref=f17e315]:
- heading "PKCE는 두 요청을 같은 주체에 묶는다" [level=3] [ref=f17e316]
- paragraph [ref=f17e317]: 처음 요청에 code_challenge를 담아서 보내고, 교환할 때 원본인 code_verifier를 보내서 이 두개가 일치하는지 확인 한다. 이 2개가 일치해야 토큰 교환이 되게 된다. code를 누가 훔쳐 가도 verifier가 없으면 token으로 바꾸지 못한다.
- listitem [ref=f17e318]:
- generic [ref=f17e319]: "04"
- generic [ref=f17e320]:
- heading "issuer 검증값과 JWK 조회 주소를 같은 값으로 맞추려 하지 않는다" [level=3] [ref=f17e321]
- paragraph [ref=f17e322]: issuer는 요청을 보내는 주소가 아니라 token의 canonical issuer identifier다. 검증은 발급된 token의 iss claim이 그 값과 같은지를 본다. JWK 조회 주소는 실제로 공개키를 가져오는 network 경로다. 이 예제에서는 브라우저가 보는 주소와 컨테이너 안에서 닿는 주소가 다르다. 컨테이너 안에서는 자기 localhost가 그 서버가 아니므로 service 이름을 써야 하고, 브라우저는 그 이름에 닿지 못한다. issuer 검증값과 endpoint 연결 주소는 따로 구성한다. 둘을 하나로 맞추려 하면 로그인 redirect가 깨지거나 서버가 키를 못 가져온다.
- listitem [ref=f17e323]:
- generic [ref=f17e324]: "05"
- generic [ref=f17e325]:
- heading "Resource API는 서명만 보고 끝내지 않는다" [level=3] [ref=f17e326]
- paragraph [ref=f17e327]: 서명이 맞다는 것은 그 IdP가 발급했다는 뜻일 뿐이다. 같은 IdP가 다른 API용으로 발급한 token도 서명은 맞다. Resource Server는 서명과 함께 issuer, 유효 시간, audience를 검증한다. 특히 audience를 검증해야 다른 resource를 대상으로 발급된 token을 현재 API에서 받아들이지 않는다.
- listitem [ref=f17e328]:
- generic [ref=f17e329]: "06"
- generic [ref=f17e330]:
- heading "redirect_uri는 exact match로 좁힌다" [level=3] [ref=f17e331]
- paragraph [ref=f17e332]: wildcard allowlist는 학습 환경에서 편하다. 다만 허용 범위가 넓으면 같은 호스트의 다른 경로로도 code가 갈 수 있다. 실제로 쓰는 callback 주소만 등록해 두면 code가 도착할 수 있는 곳이 그 하나로 줄어든다. 등록하지 않은 redirect_uri를 보냈을 때 거부하는지 확인하는 검사도 따로 둔다.
- listitem [ref=f17e333]:
- generic [ref=f17e334]: "07"
- generic [ref=f17e335]:
- heading "로그인 구간과 API 호출 구간을 한 줄로 그리지 않는다" [level=3] [ref=f17e336]
- paragraph [ref=f17e337]: 로그인 구간은 authorization request에서 시작해 callback과 code 교환을 지나 로그인 상태를 만드는 데까지다. API 호출 구간은 브라우저 입력, 중간 계층의 credential 변환, 보호 자원의 검증, 최종 응답이다. 로그인 구간과 애플리케이션 API 호출 구간은 호출 주체가 다를 수 있으므로 별도로 그린다. 그래야 code 교환 주체와 Resource Server 호출 주체를 각각 확인할 수 있다.
- region [ref=f17e338]:
- heading "적용할 때" [level=2] [ref=f17e339]
- list [ref=f17e340]:
- listitem [ref=f17e341]: Authorization Code Flow를 쓰는 client를 설정하거나 문서로 설명할 때
- listitem [ref=f17e342]: 브라우저 요청과 server-to-server 요청이 한 흐름에 섞여 있을 때
- listitem [ref=f17e343]: endpoint별로 무엇이 노출되는지 나눠야 할 때
- listitem [ref=f17e344]: PKCE 적용과 client 인증 방식을 정할 때
- region [ref=f17e345]:
- heading "예외와 주의" [level=2] [ref=f17e346]
- list [ref=f17e347]:
- listitem [ref=f17e348]: Client Credentials처럼 사용자 없이 token을 받는 흐름은 Authorization Endpoint를 지나지 않는다.
- listitem [ref=f17e349]: "Device Authorization Grant는 브라우저 redirect가 아니라 device code와 user code를 사용하므로 이 문서의 `redirect_uri` 흐름과는 별도로 본다."
- region [ref=f17e350]:
- heading "예시" [level=2] [ref=f17e351]
- list [ref=f17e352]:
- listitem [ref=f17e353]: authorization request에는 code_challenge_method=S256이 있고 client secret은 없다
- listitem [ref=f17e354]: token request에는 code_verifier가 있다. secret을 가진 client는 이 요청에서 자기를 인증한다
- listitem [ref=f17e355]: expected issuer는 http://localhost:8080/realms/keycloak-patterns 이고 JWK 조회는 컨테이너 network 주소를 쓴다
- listitem [ref=f17e356]: audience에 keycloak-pattern-api 가 없으면 invalid_token 결과가 되어 401이 된다
- listitem [ref=f17e357]: redirect allowlist에 wildcard가 있으면 등록한 host의 다른 경로로도 code가 갈 수 있다
- paragraph [ref=f17e358]: 마지막 검증 2026.08.25
- region [ref=f17e359]:
- paragraph [ref=f17e360]: Relations
- heading "이 기록과 연결된 맥락" [level=2] [ref=f17e361]
- list [ref=f17e362]:
- listitem [ref=f17e363]:
- link "브라우저가 code를 직접 교환하는 흐름에서 endpoint별 이동을 관측했다. SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계" [ref=f17e364] [cursor=pointer]:
- /url: /cases/spa-browser-credential-boundary
- generic [ref=f17e365]: 브라우저가 code를 직접 교환하는 흐름에서 endpoint별 이동을 관측했다.
- strong [ref=f17e366]: SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계
- generic [ref=f17e367]:
- listitem [ref=f17e368]:
- link "confidential client가 token endpoint에서 client 인증을 수행하는 흐름을 보여 준다. Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출" [ref=f17e369] [cursor=pointer]:
- /url: /cases/split-custody-access-token
- generic [ref=f17e370]: confidential client가 token endpoint에서 client 인증을 수행하는 흐름을 보여 준다.
- strong [ref=f17e371]: Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출
- generic [ref=f17e372]:
- complementary [ref=f17e373]:
- heading "작업 상태" [level=2] [ref=f17e374]
- status "편집 상태" [ref=f17e375]: 저장됨
- generic [ref=f17e376]:
- generic [ref=f17e377]:
- term [ref=f17e378]: 저장 버전
- definition [ref=f17e379]: "33"
- generic [ref=f17e380]:
- term [ref=f17e381]: 종류
- definition [ref=f17e382]: REFERENCE
- paragraph [ref=f17e383]: 불완전한 초안도 저장할 수 있습니다. Ctrl+S 로도 저장합니다. 게시를 누르면 채워야 할 칸을 그 자리에 표시합니다.
- generic [ref=f17e384]:
- button "저장" [disabled] [ref=f17e385]
- button "게시" [ref=f17e386]
- paragraph [ref=f17e387]: 버전 33으로 저장했습니다.