Files
DongHyeonkaandClaude Fable 5.1 4d50bb939a docs(keycloak): adopt the decomposition contract, fix the redirect URI, strip evaluative prose
- 계약 채택 — 독자 질문, 후보 29건(PROMOTE 24 · MERGE_INTO 4 · KEEP_IN_SSOT 1). 게시 중
  17건은 전부 유지. 저장소 keycloak-pattern 은 패턴 넷이 브랜치로 갈라져 있어 revisions 로
  tip 넷을 적었다. keycloak-session-store 는 같은 저장소 @ cdac9b8
- 게시된 기록의 redirect_uri 가 SSOT·코드와 달랐다 — OAuth2callback.html → callback.html
  (frontend/src/app.js 에서 확인). 계약 title 이 기록과 다른 7건도 기록 쪽으로 맞췄다
- 미작성 1건 작성 — 패턴 검증을 실제로 돌릴 때의 안전한 순서(Reference)
- 리뷰 100건 반영 — 설명 뒤에 붙은 평가·차례 예고·독자 오해 가정·작성 지시를 지웠다.
  삭제가 남긴 조각 4건을 고치고, 원래부터 잘려 있던 로컬 미리보기 라벨 1건도 닫았다

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 12:39:20 +09:00

111 lines
7.6 KiB
Markdown

---
id: ede6b9ce-eeed-40c8-9175-9e8116029395
kind: REFERENCE
slug: public-confidential-client-boundary
title: Public Client와 Confidential Client 구분 기준
topic: oauth-oidc-auth-boundary
topicName: OAuth/OIDC 인증 경계
project: KeyCloak Patterns
status: 게시 중
version: 27
verifiedOn: 2026-08-30
studio: "https://hyeonworks.com/studio/documents/ede6b9ce-eeed-40c8-9175-9e8116029395/edit"
public: "https://hyeonworks.com/references/public-confidential-client-boundary"
sourceRevision: keycloak-patterns-lab@2026-08
source:
- final/document.md#검토한-선택지와-막힌-지점-책임과-데이터
- final/document.md#선택의-이유와-지킨-경계-ap1
- final/document.md#선택의-이유와-지킨-경계-ap2
---
# Public Client와 Confidential Client 구분 기준
OAuth 클라이언트의 종류는 클라이언트 시크릿을 안전하게 보관할 수 있는지로 정한다.
SPA(Single Page Application)는 브라우저에서 실행되기 때문에 시크릿을 사용자에게 노출하지 않고 보관할 방법이 없다.
그래서 SPA는 대개 public client로 등록한다.
## 관계
- **SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계**
SPA를 public client로 등록한 이유를 실제 구성에서 확인할 수 있다.
- **Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출**
confidential client로 등록해도 액세스 토큰을 브라우저까지 보낼지는 따로 정한다.
- **Authorization Code Flow의 Endpoint와 Credential 이동 기준**
클라이언트 종류에 따라 token endpoint에서 하는 클라이언트 인증이 달라진다.
## 목적
OAuth 클라이언트를 등록하려면 이 클라이언트를 public client로 볼지 confidential client로 볼지부터 정해야 한다.
그래야 authorization code를 토큰으로 교환할 때 PKCE(Proof Key for Code Exchange)를 쓸지, 클라이언트 시크릿으로 클라이언트를 인증할지 정할 수 있다.
클라이언트 종류를 나누는 기준은 클라이언트 시크릿을 사용자에게 노출하지 않고 안전하게 보관할 수 있는지다.
SPA는 브라우저에서 실행되기 때문에 코드에 시크릿을 넣어도 개발자 도구 같은 것으로 사용자가 확인할 수 있고, 그래서 시크릿을 안전하게 보관할 수 없는 public client로 구성한다.
서버나 BFF(Backend for Frontend)는 시크릿을 서버 안에만 두고 브라우저에 전달하지 않을 수 있으므로 confidential client로 구성할 수 있다.
클라이언트 종류가 토큰을 누가 보관하는지까지 정해 주지는 않는다.
클라이언트 종류는 시크릿을 안전하게 보관할 수 있는지로 정하고, 토큰을 브라우저와 서버 중 어디에서 관리할지는 애플리케이션의 인증 구조에 따라 따로 정한다.
## 규칙
### 1. 클라이언트 시크릿을 숨길 수 있는지로 종류를 정한다
애플리케이션의 배포 파일이나 실행 중인 메모리에서 사용자가 클라이언트 시크릿을 확인할 수 있다면 그 시크릿은 안전하게 보관할 수 없으므로 public client로 본다.
시크릿을 서버 안에만 보관하고 사용자에게 전달되지 않도록 통제할 수 있다면 confidential client로 구성할 수 있다.
네이티브 앱은 브라우저에서 실행되지는 않지만 애플리케이션이 사용자 기기에 설치되기 때문에, 배포 파일을 분석하면 안에 들어 있는 클라이언트 시크릿을 확인할 수 있다. 그래서 네이티브 앱도 대개 public client로 다룬다.
### 2. public client에서 Authorization Code Flow에 PKCE를 함께 쓴다
PKCE는 authorization code가 중간에 탈취되더라도 다른 사람이 그 code를 토큰으로 바꾸기 어렵게 만드는 보호 장치다.
클라이언트 시크릿을 대신해 클라이언트를 인증하는 방식은 아니다.
로그인을 시작할 때 클라이언트는 임의의 code_verifier를 만들고, 이 값을 변환한 code_challenge를 authorization request에 함께 보낸다. 이후 authorization code를 토큰으로 교환할 때 원래의 code_verifier를 제출한다.
Authorization Server는 처음 받은 code_challenge와 맞춰 보고 같은 요청에서 시작된 교환인지 확인한다.
변환 방식은 S256을 쓴다. plain은 code_verifier 자체가 code_challenge로 전달되기 때문에 authorization request를 관찰한 사람이 그 값을 그대로 알 수 있다. S256은 code_verifier를 SHA-256으로 변환한 값을 보내므로 authorization request에 원래의 code_verifier가 실리지 않는다.
### 3. confidential client에도 PKCE를 함께 쓸 수 있다
클라이언트 인증과 PKCE는 보호하는 대상이 다르기 때문에 confidential client에서 둘을 같이 쓸 수 있다.
클라이언트 인증은 token endpoint에 요청을 보낸 쪽이 그 클라이언트가 맞는지 확인하고, PKCE는 authorization code를 받은 쪽이 로그인을 시작할 때 만든 code_verifier를 가지고 있는지 확인한다.
### 4. public client에서는 implicit flow와 direct access grant를 끈다
implicit flow는 authorization code를 거치지 않고 액세스 토큰을 브라우저의 redirect URI로 바로 전달하기 때문에, 토큰이 지나가는 구간과 노출될 수 있는 범위가 넓어진다.
direct access grant는 애플리케이션이 사용자의 아이디와 비밀번호를 직접 받아 Authorization Server에 전달하는 방식이다.
사용자가 IdP에만 주면 되는 비밀번호를 애플리케이션도 함께 다룬다.
현재 구조는 Authorization Code Flow를 쓰므로 implicit flow와 direct access grant는 비활성화했다.
### 5. 클라이언트 종류만으로 브라우저가 토큰을 받는지가 정해지지는 않는다
confidential client가 authorization code를 토큰으로 교환하더라도, 그렇게 받은 액세스 토큰을 다시 브라우저에 전달하는 구조를 만들 수 있다.
클라이언트 종류는 클라이언트 시크릿을 어디에 안전하게 보관할 수 있는지를 말한다.
액세스 토큰이 브라우저까지 가는지는 어느 계층이 실제 API 호출을 맡도록 설계했는지에 따라 따로 정해진다.
이 기준으로 등록한 클라이언트 넷 중 셋이 confidential인데, 그 셋에서 액세스 토큰이 브라우저까지 가는지는 갈렸다.
mediator 구성에서는 브라우저가 API를 직접 불러서 액세스 토큰이 응답으로 내려갔고, BFF와 프록시 구성에서는 서버 쪽이 API를 불러서 브라우저에 줄 것이 없었다.
## 적용 조건
- 새 OAuth 클라이언트를 등록할 때
- SPA와 서버 중 어디가 code를 교환할지 정할 때
- PKCE와 클라이언트 인증을 어디에 걸지 정할 때
- 기존 클라이언트의 종류가 맞는지 다시 볼 때
## 예외
- 같은 서비스가 브라우저용 public client와 서버용 confidential client를 따로 등록할 수 있다.
- 백엔드가 사용자 없이 자기 자격으로 부르는 흐름은 Client Credentials를 쓰는 별도 클라이언트다.
## 예시
- SPA용 클라이언트 : public, standard flow만 켜고 implicit flow와 direct grant는 끈다
- mediator용 클라이언트 : confidential, client_secret_basic으로 token endpoint에서 인증한다
- BFF용 클라이언트 : confidential, PKCE S256을 함께 쓴다
- 프록시용 클라이언트 : confidential, oauth2-proxy가 클라이언트 시크릿과 verifier로 code를 교환한다
- confidential client인 mediator를 써도 액세스 토큰이 브라우저 응답에 실려 나갈 수 있다