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>
108 lines
7.0 KiB
Markdown
108 lines
7.0 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"
|
|
---
|
|
|
|
# Public Client와 Confidential Client 구분 기준
|
|
|
|
OAuth Client의 종류는 client secret을 안전하게 보관할 수 있는지를 기준으로 결정한다.
|
|
SPA는 브라우저에서 실행되기 때문에 secret을 사용자에게 노출하지 않고 안전하게 보관할 수 없다.
|
|
따라서 SPA는 일반적으로 Public Client로 등록한다.
|
|
|
|
## 관계
|
|
|
|
- **SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계**
|
|
SPA를 public client로 등록한 이유를 실제 구성에서 확인할 수 있다.
|
|
- **Refresh Token 관리만 서버로 이전, Access Token은 여전히 Browser에 노출**
|
|
confidential client를 사용해도 access token 전달 방식은 별도로 설계된다는 예다.
|
|
- **Authorization Code Flow의 Endpoint와 Credential 이동 기준**
|
|
client 종류에 따라 token endpoint의 client 인증 방식이 달라진다.
|
|
|
|
## 목적
|
|
|
|
먼저 OAuth Client가 Public Client인지 Confidential Client인지 결정해야 한다.
|
|
그래야 Authorization Code를 Token으로 교환할 때 PKCE를 사용할지, client secret을 이용한 Client 인증을 사용할지 결정할 수 있다.
|
|
|
|
Client 종류를 나누는 기준은 client secret을 사용자에게 노출하지 않고 안전하게 보관할 수 있는지다.
|
|
SPA는 브라우저에서 실행되기 때문에 코드에 secret을 넣어도 개발자 도구 등을 통해 사용자가 확인할 수 있다.
|
|
따라서 SPA는 secret을 안전하게 보관할 수 없는 Public Client로 구성한다.
|
|
|
|
반면 서버나 BFF는 secret을 서버 내부에 보관하고 브라우저에 전달하지 않을 수 있으므로 Confidential Client로 구성할 수 있다.
|
|
|
|
여기서 Client 종류와 Token을 누가 관리하는지는 구분해야 한다.
|
|
Confidential Client라고 해서 반드시 Token이 서버에만 있어야 하는 것은 아니다.
|
|
Client 종류는 secret을 안전하게 보관할 수 있는지로 정하고, Token을 브라우저와 서버 중 어디에서 관리할지는 애플리케이션의 인증 구조에 따라 별도로 정한다.
|
|
|
|
## 규칙
|
|
|
|
### 1. secret을 숨길 수 있는지로 종류를 정한다
|
|
|
|
애플리케이션의 배포 파일이나 실행 중인 메모리에서 사용자가 client secret을 확인할 수 있다면 이를 안전하게 보관할 수 없으므로 Public Client로 본다.
|
|
반대로 client secret을 서버 내부에만 보관하고 사용자에게 전달되지 않도록 통제할 수 있다면 Confidential Client로 구성할 수 있다.
|
|
|
|
Native App도 브라우저에서 실행되는 것은 아니지만 애플리케이션이 사용자 기기에 설치되기 때문에, 배포 파일을 분석하면 내부에 포함된 client secret을 확인할 수 있다. 따라서 Native App 역시 일반적으로 Public Client로 다룬다.
|
|
|
|
### 2. public client에서 Authorization Code Flow에 PKCE를 함께 쓴다
|
|
|
|
PKCE는 client secret을 대신해서 Client를 인증하는 방식이 아니다.
|
|
Authorization Code가 중간에 탈취되더라도 다른 사람이 그 Code를 Token으로 교환하기 어렵게 만드는 보호 장치다.
|
|
|
|
로그인을 시작할 때 Client는 임의의 code_verifier를 만들고, 이를 변환한 code_challenge를 Authorization Request에 함께 보낸다. 이후 Authorization Code를 Token으로 교환할 때 원래의 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를 함께 쓸 수 있다
|
|
|
|
Client 인증을 사용하는 Confidential Client에서도 PKCE는 함께 사용할 수 있다.
|
|
Client 인증과 PKCE는 보호하는 대상이 다르기 때문이다.
|
|
Client 인증은 Token Endpoint에 요청한 Client가 올바른 Client인지 확인하고, PKCE는 Authorization Code를 받은 주체가 로그인 시작 시 생성한 code_verifier를 가지고 있는지 확인한다. 그래서 두 방식을 같이 사용하면 서로 다른 구간을 각각 보호할 수 있다.
|
|
|
|
### 4. public client에서는 implicit flow와 direct access grant를 끈다
|
|
|
|
Implicit Flow는 Authorization Code를 거치지 않고 Access Token을 브라우저의 Redirect URI로 직접 전달한다.
|
|
이 때문에 Token이 브라우저를 통과하고 노출될 수 있는 범위가 넓어진다.
|
|
|
|
Direct Access Grant는 애플리케이션이 사용자의 아이디와 비밀번호를 직접 받아 Authorization Server에 전달하는 방식이다.
|
|
원래 사용자가 IdP에만 제공하면 되는 비밀번호를 애플리케이션도 다루게 된다는 문제가 있다.
|
|
|
|
현재 구조에서는 Authorization Code Flow를 사용하고 있으므로 Implicit Flow와 Direct Access Grant는 비활성화했다.
|
|
|
|
### 5. Client 종류만으로 브라우저가 Token을 받는지 여부가 결정되지는 않는다.
|
|
|
|
Confidential Client가 Authorization Code를 Token으로 교환하더라도, 그 결과로 받은 Access Token을 다시 브라우저에 전달하는 구조를 만들 수 있다.
|
|
즉, Confidential Client라고 해서 Token이 반드시 서버 내부에만 있는 것은 아니다.
|
|
|
|
Client 종류는 client secret을 어디에 안전하게 보관할 수 있는지를 나타낸다.
|
|
반면 Access Token이 브라우저까지 전달되는지는 어느 계층이 실제 API 호출을 담당하도록 설계했는지에 따라 별도로 결정된다.
|
|
|
|
## 적용 조건
|
|
|
|
- 새 OAuth client를 등록할 때
|
|
- SPA와 server 중 어디가 code를 교환할지 정할 때
|
|
- PKCE와 client 인증을 어디에 둘지 정할 때
|
|
- 기존 client의 종류가 맞는지 다시 볼 때
|
|
|
|
## 예외
|
|
|
|
- 같은 서비스가 브라우저용 public client와 server용 confidential client를 따로 등록할 수 있다.
|
|
- backend가 사용자 없이 자기 자격으로 부르는 흐름은 Client Credentials를 쓰는 별도 client다.
|
|
|
|
## 예시
|
|
|
|
- SPA용 client : public, standard flow만 켜고 implicit flow와 direct grant는 끈다
|
|
- Mediator용 client : confidential, client_secret_basic으로 token endpoint에서 인증한다
|
|
- BFF용 client : confidential, PKCE S256을 함께 쓴다
|
|
- Proxy용 client : confidential, oauth2-proxy가 secret과 verifier로 code를 교환한다
|
|
- confidential client인 Mediator를 써도 access token은 브라우저 응답에 반환될 수 있다
|