--- title: branch / feature-keycloak-account-linking-spa-ux (Account Linking 정책 — SPA 컨텍스트의 사용자 노출) source_type: branch-note status: raw id: BR-KEYCLOAK-CHILD-1666E2E0 kind: branch-child project: keycloak-patterns-overview work_item: WI-KEYCLOAK-PATTERNS-OVERVIEW-020 inherits: [DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1] refines: [] overrides: [] depends_on: [] contract_packet: 1 branch: feature-keycloak-account-linking-spa-ux parent_branch: feature-keycloak-patterns related_projects: [keycloak-patterns] tags: [branch, keycloak-patterns, spa, idp-brokering, google-federation, account-linking, p2b] created: 2026-05-25 target_merge: status_label: in-progress contract_packet_sha256: 90ab9a8505129356c2a13f017db9741ca882313952664fa7262177c192b79286 --- # branch: feature-keycloak-account-linking-spa-ux (Account Linking 정책 — SPA 컨텍스트) > Layer: `raw/branch-notes/` — [[raw/branch-notes/feature-keycloak-patterns]]의 branch-child. > 학습 노트. P2B는 `documented-only` 단계. ## 부모 (필수) [[raw/branch-notes/feature-keycloak-patterns]] ## 브랜치 계약 패킷 - **생성 시 프로젝트 개정**: `1` - **패킷 스키마**: `contract_packet: 1` - **완료 조건**: project governance hub가 AP1~AP4 taxonomy와 child progress index를 유지한다 ### 상속한 프로젝트 결정 | Decision Ref | Project Summary | Branch Application | Source | |---|---|---|---| | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-AUTH-TAXONOMY-001@1` | canonical 분류축은 AP1~AP4 인증 통합 아키텍처와 cross-cutting 변형이다 | account-linking SPA UX를 IdP-brokering cross-cutting 학습 자료로 분류한다 | [[raw/project-notes/keycloak-patterns-overview]] | ### 브랜치 지역 결정 | Decision ID | Decision | Relation | Supporting Claims | Status | |---|---|---|---|---| | D1 | automatic email linking 대신 confirm flow를 사용한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` | | D2 | SPA UX는 linking trust policy owner의 결론을 소비한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` | | D3 | link 상태 표시는 Account REST API 검증 대상으로 둔다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` | | D4 | unlink UX는 Account Console을 기본 진입점으로 둔다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` | | D5 | link trigger는 Client Initiated Account Linking을 사용한다 | `local` | [[raw/project-notes/keycloak-patterns-overview]] | `proposed` | ### 선언한 예외 | Override ID | Overrides | Reason | Approval | Status | |---|---|---|---|---| 없음. ## 목표 P1B의 Account Linking 보안 정책(`sub` primary key + Confirm Link Existing Account)을 **SPA 컨텍스트에서** 어떻게 사용자에게 노출할지 정리. P1B는 oauth2-proxy가 cookie session을 다루지만, P2B는 SPA가 직접 token을 다루므로 **UX 노출 지점이 다름**. 면접 질문: "기존 Keycloak 사용자가 나중에 Google 로그인을 추가하려면 어떤 흐름인가요?" → "Keycloak의 Account Console에서 'Linked Accounts' 메뉴를 통해 Google 계정을 link합니다. 또는 로그인 화면에서 Google로 처음 로그인했을 때 같은 email의 기존 계정이 있으면 'Confirm Link Existing Account' 뒤에 owner flow가 선택한 Email verification 또는 password re-authentication이 실행됩니다. SPA는 이 흐름에 직접 관여하지 않고, Keycloak이 redirect로 처리합니다. SPA는 link 완료 후 access token을 받고, 자체 UI로 'Google 계정 연결됨'을 표시할 수 있습니다 (token claim 또는 별도 API 호출)." - 이슈: - PR: ## 범위 ### 포함 범위 - **3가지 사용자 시나리오**: 1. **신규 Google 사용자** (Keycloak에 같은 email 없음) → First Broker Login Flow가 자동 user 생성 2. **기존 Keycloak local 사용자**가 Google 로그인 시도 (같은 email) → Confirm Link Existing Account → owner flow의 verification profile(Email 또는 password re-authentication) 통과 후 link 3. **이미 link된 사용자** → 평상 로그인 - Account 관리 진입점 비교 (SPA 관점): - Keycloak Account Console (`/realms/{r}/account/`) — Keycloak이 제공하는 user-facing UI - SPA 자체 UI + Keycloak Admin REST API - Keycloak Account REST API (사용자 자신의 데이터 조작) - SPA가 "Account linked"를 표시하는 방법 - access token claim에 `federated_identities` 포함 불가 (기본). Account REST API 호출 또는 별도 backend endpoint. - unlink 흐름 — Keycloak Account Console의 "Linked Accounts" 메뉴 ### 제외 범위 - 코드 변경 없음 검증 — [[raw/branch-notes/feature-keycloak-federation-spa-zero-change]] - claim mapping — [[raw/branch-notes/feature-keycloak-idp-mappers-claim-to-role]] - JWT signature 검증 — [[raw/branch-notes/feature-keycloak-three-leg-trust-chain]] - P1B와의 흐름 동일성은 [[raw/branch-notes/feature-keycloak-edge-forwardauth-google-federation]] (Edge proxy 컨텍스트) 참고 ## 근거 (필수, 최소 1개+) - [[raw/official-docs/keycloak-first-login-flow]] — D1 근거: email 자동 link 는 security hole 공식 경고 + Confirm Link info page 동작 - [[raw/official-docs/keycloak-client-initiated-account-linking]] — D5 근거: SPA/client 가 계정 링크를 트리거하는 공식 메커니즘("Client Initiated Account Linking" — 서명된 redirect URL fabrication + `account.manage-account`/`account.manage-account-links` role) + `keycloak.login({action:'link'})` built-in 가정 미확인(does-not-prove) - [[raw/official-docs/google-oidc-discovery-spec]] — D2 위임 컨텍스트: Google `sub` 은 unique/never-reused(`GOOGLE-OIDC-C6`) → email 아닌 `sub` 기반 매칭 근거. [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D4 — linking trust policy를 consume한다. ## TODO - [ ] First Broker Login Flow의 default authenticator 흐름 정리 — 등급: `documented-only` - [ ] **Auto-Link 보안 위험** — default `Automatically Set Existing User` 사용 시 hijack 가능. 본 패턴은 사용 금지 — 등급: `documented-only` - [ ] **Confirm Link Existing Account authenticator** 채택 — 후속 verification 방식은 [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D2 — owner profile을 consume — 등급: `documented-only` - [ ] 신규 Google 사용자 first-time 경험: review profile (옵션) → 자동 user 생성 → SPA로 redirect — 등급: `documented-only` - [ ] 기존 사용자 link 시점 — login 화면에서 자동 트리거 vs Account Console에서 명시적 link — 등급: `documented-only` - [ ] SPA가 "현재 link된 IdP 목록" 표시하려면 — Keycloak Account REST API (`GET /realms/{r}/account/linked-accounts`) 호출 필요 — 등급: `needs-confirmation` - [ ] unlink 흐름 — Account Console "Linked Accounts" → Remove. unlink 후 해당 IdP로 로그인 시 다시 first broker login 흐름 — 등급: `documented-only` - [ ] 로컬 비밀번호 없는 사용자가 마지막 federated identity를 unlink하면 Keycloak 엔진이 거부 — [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] D3 — source-grounded, 배포 release tag 재확인 필요 — 등급: `documented-only` - [ ] SPA에서 link/unlink 트리거 시 redirect 흐름 — `keycloak.login({ action: 'link', idpHint: 'google' })` 가능 여부 — 등급: `needs-confirmation` → **조사 반영(2026-07-15)**: 공식 메커니즘은 built-in login action 이 아니라 서명 redirect URL fabrication(D5, [[raw/official-docs/keycloak-client-initiated-account-linking]]). adapter helper 존재 여부만 잔여 확인. ## 진행 중 메모 - **P1B와의 핵심 차이**: P1B는 oauth2-proxy가 cookie session으로 사용자 상태를 가짐. SPA가 없으므로 "Account linked" UI는 별도 페이지(예: `/account/`)로 redirect. P2B는 SPA가 SPA 안에서 "내 계정" 화면을 그리고, link 상태는 API 호출로 가져옴. - **하지만 brokering 흐름 자체는 동일** — Keycloak이 First Broker Login Flow를 실행하고, SPA / proxy는 결과만 받음. - **Auto-Link의 위험** (재확인): - 시나리오: 공격자가 `victim@example.com`로 Google 가입 (Google은 `email_verified=true` 표시) → 그 Google 계정으로 Keycloak 로그인 → 만약 Auto-Link면 `victim@example.com` Keycloak local 계정으로 자동 link → **계정 탈취**. - 방어: `email_verified` 검증만으로는 부족하다. **Confirm Link Existing Account 뒤 소유 증명**이 본질이며, 구체 수단은 [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D2의 조건부 profile(Email 기본 또는 password 재인증 관철/폴백)을 따른다. - **SPA가 link 상태를 표시하는 방법**: 1. Keycloak Account REST API `/realms/{r}/account/linked-accounts` 호출 (SPA가 자기 access token 사용 — `account` audience 필요) 2. 또는 backend가 Keycloak Admin API를 통해 `GET /admin/realms/{r}/users/{id}/federated-identity` 호출 후 SPA에 노출 (backend는 service account 사용) - **unlink 후 재로그인**: link 해제하면 Federated Identity record가 삭제됨. 다시 Google로 로그인하면 First Broker Login Flow가 다시 실행 → Confirm Link Existing Account가 다시 트리거됨. - **orphan account 방지**: [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] D3 — Keycloak 엔진의 마지막 federated identity 제거 guard를 consume한다. 본 branch는 HTTP 400 UX 처리만 소유하며, 배포 release tag 동일성은 `needs-confirmation`이다. - **조사 반영(2026-07-15, `/branch-spec`)**: - **Claim #4 정정** — keycloak-js 에 `keycloak.login({action:'link'})` 같은 built-in link action 은 KC-CIAL 로 확인 안 됨. 공식 경로는 앱이 `broker/{provider}/link` redirect URL 을 hash 서명과 함께 **직접 fabricate**(D5). keycloak-js/securing-apps 문서 모두 built-in link 메서드를 다루지 않음. - **D2 재framing** — `email_verified` 는 Keycloak 의 *linking gate* 가 아니라 **Trust Email**(외부 IdP 계정 생성 시 verified 표시) 설정(Keycloak admin 문서, WebSearch 비아카이브). [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D4 — linking trust policy를 정본으로 참조하며, 이 branch는 메커니즘을 복제하지 않는다. - **CVE 참고** — first-broker-login 의 cross-session email verification proof 가 upstream identity 에 bound 되지 않는 취약점(CVE-2026-9087, keycloak/keycloak#49175, 비아카이브)이 존재 → 실 구현 단계에서 Keycloak 버전 patch 확인 대상. ## 결정 사항 > 추후 면접/회고에서 "왜 이렇게 했나" 답할 근거. 대안과 함께 기록. - 2026-05-25: **Auto-Link 금지 / Confirm Link Existing Account 채택** — 이유: 같은 email의 기존 계정 hijack 방지. (P1B와 동일 정책) - 2026-05-25 (**Historical / superseded**): `email_verified=true`만 link 허용한다는 초기 판단. Active policy는 [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D4 — linking trust policy를 참조한다. - 2026-05-25: **SPA의 link 상태 표시 = Keycloak Account REST API 호출** — backend 거치지 않고 SPA가 직접. 이유: backend 코드 추가 0 (P2A와 동일하게 유지). Account API audience(`account`)가 SPA token에 자동 포함되는지 확인 필요 (`needs-confirmation`). - 2026-05-25: **unlink 흐름 = Keycloak Account Console 사용** — SPA 자체 UI는 학습 단계에선 미구현. 운영 시 SPA 안에 카드형 UI 추가 검토. - 2026-07-15 (`/branch-spec` 조사): **D5 추가 — SPA 가 link 를 트리거하는 공식 메커니즘 = Client Initiated Account Linking (서명된 redirect URL fabrication)** — keycloak-js built-in login action 이 아님. 검토한 대안: (a) `keycloak.login({action:'link'})` built-in — 공식 근거 없음, (b) 앱이 `broker/{provider}/link?...&hash=` URL 직접 구성 — 공식(KC-CIAL). 근거: [[raw/official-docs/keycloak-client-initiated-account-linking]]. - 2026-07-15 (`/branch-spec` 재framing): **D2 는 이 브랜치가 소유하지 않고 위임(consume)** — [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D4 — linking trust policy. 이 브랜치는 SPA 컨텍스트에서 동일 게이트를 재진술하지 않고 참조만 한다(consistency-contract Single-Owner). ## 결정-근거 매핑 > 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시한다. > `Decision ID` 는 이 branch-note 안에서 안정적으로 유지한다. > `Supporting Claims` 는 `raw//.md#` 형식. | Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk | |---|---|---|---|---|---| | D1 | Auto-Link 금지 / Confirm Link Existing Account 채택 (같은 email 의 기존 계정 hijack 방지) | 외부 IdP email 을 항상 신뢰 못할 때(=일반 케이스) → Confirm Link. 폐쇄망에서 IdP 를 완전 신뢰하면 Trust Email + auto-link 도 가능하나 본 패턴 미채택 | `raw/official-docs/keycloak-first-login-flow.md#KC-FLF-C2`, `raw/official-docs/keycloak-first-login-flow.md#KC-FLF-C3` — flow *구성*(정확한 authenticator step)은 owner [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D1(auto-link `DISABLED`)·D2(Confirm Link + Verify Existing Account — SMTP 시 Email 기본 / Re-auth 는 email authenticator DISABLE 시)에 위임 | `official-vendor-doc` (공식 경고 + Confirm Link info page 동작) | "Confirm Link Existing Account" 가 default flow 에 포함되는지 vs 별도 추가 필요한지 Keycloak version 별 확인 필요 (owner 브랜치 D2 에서 추적) | | D2 | SPA UX는 linking trust policy를 새로 정하지 않고 owner 결론을 consume | 정책을 바꾸려면 owner에서 변경. 이 branch는 사용자 노출과 상태 표시만 다룸(Reference-Only) | [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D4 — linking trust policy | `delegated` | `Trust Email`은 linking gate가 아니다. custom SPI artifact가 확인되지 않은 상태에서 이 branch가 별도 hard-reject를 주장하지 않음 | | D3 | SPA 의 link *상태 표시* = Keycloak Account REST API (`GET /realms/{r}/account/linked-accounts`) 직접 호출 | backend 없이 SPA 가 상태 표시해야 → Account REST API 시도(미문서화, `needs-confirmation`). backend 가 이미 있으면 → Admin API `federated-identity`(문서화, service account)가 안전 | UNSUPPORTED_DECISION | — | KC-CIAL(신규 Source)이 이 `linked-accounts` read endpoint 를 **다루지 않음을 명시 확인**(does-not-prove) + 커뮤니티상 undocumented(WebSearch). `account` audience 자동 포함 여부는 Claims To Verify 로 이관 | | D4 | unlink UX 진입점 = Keycloak Account Console (SPA 자체 UI 미구현) — UX 진입점 선택만 소유 | 학습 단계 → 기본 Account Console UI(코드 0). 운영에서 in-SPA unlink UX 필요 → SPA 자체 카드 UI(별도 구현) | UNSUPPORTED_DECISION (UX 진입점 선택); [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] D3 — unlink 안전정책 owner | — | 엔진 guard는 source-grounded이나 인용 기준이 Keycloak `main`이므로 배포 release tag 재확인 필요. 본 branch는 400 UX만 결정 | | D5 | SPA 가 link 를 **트리거**하는 방법 = Client Initiated Account Linking (앱이 서명된 redirect URL 을 fabricate), keycloak-js built-in login action 아님 | 로그인된 사용자에게 "지금 Google 연결" 버튼 제공 → client-initiated linking URL fabricate(D5). 최초 로그인 시 자동 link 은 First Broker Login(D1) 경로 — 트리거 시점이 다름 | `raw/official-docs/keycloak-client-initiated-account-linking.md#KC-CIAL-C1`, `raw/official-docs/keycloak-client-initiated-account-linking.md#KC-CIAL-C2`, `raw/official-docs/keycloak-client-initiated-account-linking.md#KC-CIAL-C3`, `raw/official-docs/keycloak-client-initiated-account-linking.md#KC-CIAL-C4` | `official-vendor-doc` | SPA(브라우저)가 hash 재료(`token.getSessionState()`/`token.getIssuedFor()`)를 keycloak-js `tokenParsed` 에서 얻는지 코드 미확인(`planned`). `KC-CIAL-C4` does-not-prove: 문서가 "CSRF 를 완전히 막지는 못한다" 경고 → SPA state 별도 방어 필요 | ## 구현 가이드 > *결정(Decisions)* 이 "*무엇*" 이라면 본 §는 "*어디에 어떻게*" 의 사전 명세. 본 브랜치는 `documented-only` 학습 노트이므로, 구현 detail 은 **공식 문서가 규정하는 프로토콜·값**을 anchor 로 하고 코드 미확인 항목은 `planned`/`UNSUPPORTED_IMPL_DECISION` 로 명시한다. ### 1. 계정 링크 트리거 — Client Initiated Account Linking (SPA → Keycloak) > **Trace**: D5 + `KC-CIAL-C1`/`C2`/`C3`/`C4`. 이미 로그인된 SPA 사용자가 "Google 연결" 버튼을 눌렀을 때의 사전 명세. > > - **UNSUPPORTED_IMPL_DECISION**: (a) SPA(브라우저)가 hash 재료 `token.getSessionState()`·`token.getIssuedFor()` 를 keycloak-js `tokenParsed.session_state`·`tokenParsed.azp` 로 얻는 매핑 — KC-CIAL 예시는 Java Servlet 전용(`KC-CIAL-C3` does-not-prove), JS 매핑은 미확인. trade-off: 학습 단계엔 이 매핑을 `planned` 로 남기고 실 구현 시 keycloak-js `tokenParsed` 필드로 검증. (b) 브라우저에서 SHA-256 계산은 `crypto.subtle.digest('SHA-256', ...)` + Base64URL 인코딩으로 수행 — 표준 Web Crypto 이나 KC-CIAL 이 JS 구현을 규정하지 않으므로 `UNSUPPORTED_IMPL_DECISION`. | 항목 | 값 / 명세 | 근거 | |---|---|---| | redirect URL 템플릿 | `{auth-server-root}/auth/realms/{realm}/broker/{provider}/link?client_id={id}&redirect_uri={uri}&nonce={nonce}&hash={hash}` | `KC-CIAL-C3` | | `provider` | `google` (Keycloak IdP alias) | D5, `KC-CIAL-C3` (provider 파라미터) | | `hash` 계산 | Base64URL( SHA_256( `nonce` + `token.getSessionState()` + `token.getIssuedFor()` + `provider` ) ) | `KC-CIAL-C4` | | 전제조건 | 사용자에 `account.manage-account` 또는 `account.manage-account-links` role + 그 role 의 scope 가 access token 에 부여 + 앱이 자기 access token 접근 | `KC-CIAL-C2` | | 목적(왜 hash) | auth server 가 client 가 요청을 시작했음을 보장(rogue app 임의 link 방지) — 단 CSRF 완전 방지는 아님 | `KC-CIAL-C4` | ### 2. 최초 로그인 링크 경로 — First Broker Login (Google 로그인 화면 → Keycloak) > **Trace**: D1 + `KC-FLF-C2`/`C3`/`C4`. §1 과 다른 트리거 시점(로그인 시 email collision). > > - **OUT_OF_BRANCH_SCOPE**: [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D2 — Confirm Link 이후 verification profile의 정본. 이 브랜치는 owner 결과에 따른 UX만 기술한다. | 시나리오 | 동작 | 근거 | |---|---|---| | 신규 Google 사용자(같은 email 없음) | 자동 user 생성. Review Profile mode 에 따라 확인 페이지(`On`/`missing`/`Off`) | `KC-FLF-C4` | | 같은 email 기존 사용자 | Confirm Link Existing Account info page → (A) profile 재검토 후 다른 email/username, (B) 기존 계정 link 확인 | `KC-FLF-C3` | | 금지 | `Automatically Set Existing User`(auto-link) — email 자동 link 는 security hole | `KC-FLF-C2` | ### 3. 링크 상태 표시 (Read path) > **Trace**: D3 (UNSUPPORTED_DECISION). SPA 가 "현재 연결된 IdP" 를 그리는 방법. > > - **UNSUPPORTED_IMPL_DECISION**: 옵션 A 의 `/account/linked-accounts` endpoint 는 KC-CIAL 가 다루지 않고 커뮤니티상 undocumented. trade-off: **backend-zero(옵션 A, 미검증)** vs **문서화된 안정성(옵션 B, backend 코드 추가)**. 학습 단계엔 옵션 A 를 `planned` 로 시도하되 실패 시 옵션 B fallback. | 옵션 | 호출 | 인증 | 상태 | |---|---|---|---| | A | SPA → `GET /realms/{r}/account/linked-accounts` | SPA access token (`account` audience 필요) | `needs-confirmation` (undocumented) | | B | backend → `GET /admin/realms/{r}/users/{id}/federated-identity` | service account | `documented-only` (Admin API) | ### 4. Unlink > **Trace**: D4 (UNSUPPORTED_DECISION, UX 진입점). [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] D3 — 안전정책 owner. > > - **UNSUPPORTED_IMPL_DECISION**: Account Console UI를 재사용할지 SPA 자체 unlink 카드를 만들지는 project UX 선택이다. orphan 거부 자체는 owner D3의 엔진 guard를 consume하며, 이 branch는 HTTP 400 안내를 처리한다. ## 엣지·실패·의존 > R4 캡처용. 정상 경로 외 실패/엣지/의존. - **실패·엣지 경로**: - **hash 누락/불일치 (§1)**: `broker/{provider}/link` 에 `hash` 가 없거나 틀리면 auth server 가 link 요청을 client-initiated 로 인정하지 않음(`KC-CIAL-C4`). 단 문서가 "CSRF 를 완전히 막지 못함" 경고 → SPA 는 별도 `state` 로 CSRF 방어 필요. - **Auto-Link hijack (§2)**: 공격자가 `victim@example.com` 로 Google 가입 후 그 IdP 로 로그인 → auto-link 면 계정 탈취. 방어 = D1 Confirm Link + [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D2의 owner-selected verification profile(Email 기본 또는 password 재인증 관철/폴백); `email_verified` 만으로는 불충분(`KC-FLF-C2`). - **orphan account (§4)**: [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] D3 — 마지막 federated identity 제거 guard를 consume. 배포 release tag가 owner 근거와 같은지는 `needs-confirmation`. - **unlink 후 재로그인 (§4)**: Federated Identity record 삭제 → 다시 Google 로그인 시 First Broker Login 재실행 → Confirm Link 재트리거(D1 경로). - **`account` audience 부재 (§3)**: SPA access token 의 `aud` 에 `account` 미포함 시 옵션 A 는 401/403 → 옵션 B fallback 필요. - **CVE-2026-9087 (§2)**: first-broker-login 의 cross-session email verification proof 가 upstream identity 에 bound 안 됨(keycloak/keycloak#49175, 비아카이브). 실 구현 시 Keycloak 버전 patch 상태 확인. - **다른 계약 의존**: - [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D1 — silent auto-link 방지 owner. [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D2 — verification profile owner. - [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] D1 — persistent federation key owner. [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] D3 — unlink guard owner. - [[raw/branch-notes/feature-keycloak-first-broker-login-flow]] D4 — linking trust policy; 본 branch는 UX만 consume. - [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]] D4 — Google claim→user attribute mapping owner. ## 검증해야 할 주장 > 공식 문서 근거가 있어도 내 프로젝트에서의 동작을 자동으로 보장하지 않는다. | Claim | Why uncertain | How to verify | Status | |---|---|---|---| | "Confirm Link Existing Account" authenticator 가 P2B 의 default first-broker-login flow 에 자동 포함된다 | KC-FLF-C3 는 authenticator 의 동작만 정의하며, default flow inclusion 여부는 Keycloak version (26.x) 별 admin UI 에서 확인 필요 | Keycloak admin console > Authentication > Flows > First Broker Login 의 step list 캡처 | `needs-confirmation` | | SPA access token 의 `aud` claim 에 `account` audience 가 자동 포함되어 Account REST API 호출 가능 | 본 branch 의 Sources 에 audience mapping 동작 문서 없음. Keycloak default client 설정 의존 | dev 환경에서 SPA access token decode 후 `aud` 필드 확인 + `GET /realms/{r}/account/linked-accounts` 호출 결과 200 확인 | `needs-confirmation` | | Keycloak Account REST API 에 `linked-accounts` read endpoint 가 실제로 존재하고 SPA 로 호출 가능하다 | KC-CIAL 은 이 endpoint 를 다루지 않음(does-not-prove); 커뮤니티상 undocumented(WebSearch) | dev 환경에서 실 호출 + 응답 schema 확인, 또는 옵션 B(Admin API `federated-identity`)로 대체 | `needs-confirmation` | | 인용한 Keycloak 엔진 unlink guard가 배포 release tag에서도 동일하게 동작한다 | [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] D3의 근거는 Keycloak `main` source이며 배포 image tag와 동일성은 아직 확인하지 않음 | 배포 tag의 `LinkedAccountsResource` guard 대조 + Google-only 사용자로 마지막 link 제거 시 HTTP 400 확인 | `needs-confirmation` (owner decision은 source-grounded) | | SPA 에서 link 트리거는 built-in `keycloak.login({action:'link'})` 가 아니라 `broker/{provider}/link` 서명 URL fabrication 이다 | KC-CIAL(`KC-CIAL-C1`/`C3`)은 앱이 URL 을 직접 fabricate 함을 규정하나 adapter API 표면은 다루지 않음 — keycloak-js 가 helper 를 제공하는지는 미확인 | keycloak-js 공식 adapter 레퍼런스에서 `login()` action 지원 목록 확인 + dev 환경에서 서명 URL 수동 구성 테스트 | `needs-confirmation` | ## 마주친 문제 - (학습 단계, 미실행) - **잠재적 함정**: SPA가 Account REST API를 호출할 때 access token의 `aud`에 `account`가 포함되어야 함. Keycloak 기본 client 설정에서 `account` audience가 자동 포함되는지 확인 필요. ## 묶음 - [[raw/official-docs/keycloak-client-initiated-account-linking]] - [[raw/official-docs/keycloak-first-broker-login-flow]] - [[raw/official-docs/keycloak-first-login-flow]] > 본 sub-sub-branch 는 leaf — 자식 자료 없음. Phase 3 P3A 실 구현 또는 외부 산출물 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화. ### 오류 기록 (이 sub-sub-branch 작업 중 발생) - (없음 — 현재 documented-only 단계) ### 면접 준비 (이 작업에서 나올 수 있는 면접 질문) - (없음 — Phase 3 실 구현 단계에 누적) ## 관련 일일 노트 ## 완료 후 정리 > 학습 노트. P2B는 `documented-only` 유지. - PR 링크: - 리뷰 메모: - 머지 결과 / 배포 환경: 학습 노트 (`documented-only`) - **wiki 추출 대상**: - `actually-implemented` 항목: (없음) - `locally-verified` 항목: (없음) - `prod-verified` 항목: (없음) - **추출하지 않을 항목**: 전 항목 (`documented-only` / `needs-confirmation`)