--- title: branch / feature-keycloak-first-broker-login-flow (First Broker Login Flow — Confirm Link Existing Account) source_type: branch-note status: raw id: BR-KEYCLOAK-PATTERNS-OVERVIEW-016 kind: project-work-item project: keycloak-patterns-overview work_item: WI-KEYCLOAK-PATTERNS-OVERVIEW-016 inherits: [DEC-KEYCLOAK-PATTERNS-OVERVIEW-IDP-BROKERING-001@1, DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1] refines: [] overrides: [] depends_on: [WI-KEYCLOAK-PATTERNS-OVERVIEW-015] contract_packet: 1 branch: feature-keycloak-first-broker-login-flow parent_branch: related_projects: [keycloak-patterns] tags: [branch, keycloak-patterns, p1b, first-broker-login, account-linking, security, account-takeover, email-verified] created: 2026-05-25 target_merge: status_label: in-progress contract_packet_sha256: 342adc2532438c3f05c868020a6cc7fdfc2a3322fddceed4180f75fd258fd6c0 --- # branch: feature-keycloak-first-broker-login-flow (First Broker Login Flow — Confirm Link Existing Account) > Layer: `raw/branch-notes/` — [[raw/project-notes/keycloak-patterns-overview]]의 `WI-KEYCLOAK-PATTERNS-OVERVIEW-016` 직접 branch. > **분류축 교정 반영 (hub 2026-07-14)**: 신 primary 축에서 본 branch 는 **Google IdP brokering cross-cutting 그룹 (hub §8.0 그룹 5)** 의 Tier-2 구현 branch다. > **done-bar (hub §1 성공기준 cross / §8.0 그룹 5)**: `email_verified=false` auto-linking 계정탈취 **재현 → Confirm Link Existing Account 로 차단**, before/after 기록, 목표 등급 `locally-verified`. (기존 "documented-only / 실 구현 안 함" 프레이밍은 2026-07-14 재편으로 stale — §Audit `FRAMING_DRIFT` 참조.) > `status_label`: `in-progress` ## 부모 (필수) [[raw/project-notes/keycloak-patterns-overview]] ## 브랜치 계약 패킷 - **생성 시 프로젝트 개정**: `1` - **패킷 스키마**: `contract_packet: 1` - **완료 조건**: unsafe auto-linking을 재현하고 Confirm Link Existing Account로 차단한다 ### 상속한 프로젝트 결정 | Decision Ref | Project Summary | Branch Application | Source | |---|---|---|---| | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-IDP-BROKERING-001@1` | Google federation은 Keycloak IdP brokering과 hardened First Broker Login을 사용한다 | First Broker Login의 계정 연결 방어 흐름에 적용한다 | [[raw/project-notes/keycloak-patterns-overview]] | | `DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1` | done-bar는 E2E success와 signature security failure 재현·해결 evidence다 | unsafe auto-linking의 재현과 차단 evidence를 완료 조건으로 사용한다 | [[raw/project-notes/keycloak-patterns-overview]] | ### 브랜치 지역 결정 > 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다. | Decision ID | Decision | Relation | Supporting Claims | Status | |---|---|---|---|---| ### 선언한 예외 | Override ID | Overrides | Reason | Approval | Status | |---|---|---|---|---| 없음. ## 목표 Keycloak의 기본 First Broker Login Flow는 "Automatically Link Existing Account by Email" 옵션을 포함한다. 이는 **편의성을 위해 보안을 희생**한 default이며, Google이 `email_verified=false`인 사용자도 발급할 수 있는 상황을 고려하면 account takeover 위험이 있다. 본 노트는 **"Confirm Link Existing Account"** 변형 flow로 변경하는 방법과 그 의미를 정리한다. > ⚠️ **2026-07-16 조사 정정 (위 전제 수정)**: "기본 flow 가 auto-link 를 *포함*한다"는 부정확하다. OOTB Default First Broker Login flow 는 `Create User If Unique`(충돌 감지 key = **email/username**, `KC-FBLVERIFY-C5`) → `Handle Existing Account`(= Confirm Link Existing Account + Verify Existing Account) 경로가 **이미 기본값**이고, email 로 *자동* link 하는 `Automatically Set Existing User`(AutoLink) 는 별도로 추가해야 하는 opt-in dangerous authenticator 다(공식 WARNING `KC-FBLVERIFY-C4`). 따라서 본 branch 의 done-bar 는 "기본에서 auto-link 를 *제거*"가 아니라 **함정을 *재현*하려면 AutoLink 를 명시 추가한 뒤, 기본 Confirm Link 로 되돌려 차단**하는 것이다(§구현 가이드, §Audit `CLAIM_DRIFT`). 원문은 verbatim 보존. **위험 시나리오 (Auto Link 사용 시):** 1. 공격자가 자신의 Google 계정 email을 `victim@example.com`으로 위장 (Google이 `email_verified=false`로 발급) 2. Keycloak에 이미 `victim@example.com`으로 가입된 local 계정 존재 3. Auto Link가 email match만 보고 두 계정을 link → 공격자가 Google 로그인으로 피해자 계정 접근 **해결:** "Confirm Link Existing Account" flow는 link 전에 **사용자가 기존 Keycloak 계정 password를 입력**(또는 email 확인)해야 하므로, Google 계정만으로는 link 불가. - 이슈: (없음 — 학습 프로젝트) - PR: (없음 — 코드 repo `/home/donghyeon/workspace/keycloak-patterns/` 아직 미생성, §Audit `NO_GROUND_TRUTH`) ## 범위 ### 포함 범위 - **First Broker Login Flow 의 *구성* (authenticator step 값)** — 이 branch 가 owner (sibling [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] 이 flow 구성을 본 branch 로 위임): - D1: `Automatically Set Existing User`(AutoLink) **미사용/DISABLED**, 기본 Confirm Link 경로 유지. - D2: `Confirm Link Existing Account` + `Verify Existing Account`(Email 기본 / Re-authentication fallback) 강제. - D3: `Review Profile` 모드 결정 (Off 권장). - D4: `email_verified=false` silent auto-link 차단 (= core D1+D2). [[raw/branch-notes/feature-keycloak-idp-brokering-google-client]] D6 (`trustEmail=false`)는 defense-in-depth로만 consume. - **함정 재현 → 차단 E2E 절차** (done-bar): AutoLink 로 계정탈취 재현 → Confirm Link 로 차단, before/after 기록. ### 제외 범위 > 의도적으로 제외한 것. 면접 등에서 "이건 범위에 없었습니다"라고 답할 근거. - **Account linking primary key (`sub` vs email) 선택** → [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] D1 (owner). - **`trustEmail` IdP 설정값 / Google client 등록 / discovery** → [[raw/branch-notes/feature-keycloak-idp-brokering-google-client]] (owner; D6 = `trustEmail=false`). - **Google claim → attribute mapper 구성 / Sync Mode 값** → [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]]. - **SPA 측 Confirm Link redirect/return UX** → [[raw/branch-notes/feature-keycloak-account-linking-spa-ux]]. - **`sub` 기반 충돌 감지 전용 custom authenticator** — OOTB `Create User If Unique` 는 email/username 매칭(`KC-FBLVERIFY-C5`). sub-only 매칭·`email_verified` hard-reject 는 커스텀 SPI authenticator 필요 → **hub §5 Deferred(server-side SPI 트랙)** (§Audit `OUT_OF_BRANCH_SCOPE`). - 비-Google IdP / SAML federation. ## 근거 (필수, 최소 1개+) | Source | 정당화하는 결정 | |---|---| | [[raw/official-docs/keycloak-first-login-flow]] | D1(auto-link = "potential security hole" 공식 경고 `KC-FLF-C2`), D2(Confirm Link info page review/link 선택 `KC-FLF-C3`), D3(Review Profile 3모드 On/missing/Off `KC-FLF-C4`) | | [[raw/official-docs/keycloak-first-broker-login-verify-authenticators-official]] | D1(AutoLink 공식 WARNING `KC-FBLVERIFY-C4` + OOTB 충돌감지 key=email/username `KC-FBLVERIFY-C5`), D2(Verify Existing Account By Email = SMTP 시 `ALTERNATIVE` 기본 `KC-FBLVERIFY-C1`, 재인증 관철=email DISABLE `KC-FBLVERIFY-C2`, Re-auth=fallback `KC-FBLVERIFY-C3`) | | [[raw/official-docs/google-openid-connect-oidc]] | D3(`email` claim 은 `email` scope 시 제공 `GOIDC-C4`); D4 전제(`email_verified` 발급 조건은 본 인용 **범위 밖** → Claims To Verify) | | [[raw/company-tech-blogs/keycloak-google-login-codemancers]] | D2 corroboration — 실무 Confirm flow 적용 사례 (`company-case-study`, **공식 best practice 로 단정 금지**) | | [[raw/official-docs/keycloak-identity-brokering-overview-official]] | First Login Flow Override(IdP 별 flow 지정) 메커니즘 배경 | | (위임) [[raw/branch-notes/feature-keycloak-idp-brokering-google-client]] `D6` | `trustEmail=false` — D4 email_verified 방어의 IdP 설정 lever (본 branch 재진술 금지, 참조만) | | (위임) [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] `D1` | `sub` 기반 linking key — 본 flow 가 정합해야 할 linking 정책 | ## TODO 각 항목 옆에 증거 등급. 본 branch 는 hub §8.0 그룹 5 의 Tier-2 구현 branch (목표 `locally-verified`) — 코드 repo 생성 전까지 대부분 `planned`. - [ ] First Broker Login Flow 기본 구조 정리 — 등급: `documented-only` - `Review Profile` (신규 사용자 프로필 확인 페이지) - `Create User If Unique` (federated identity 없으면 신규 user 생성) - `Automatically Link Existing Account` (기본 옵션 — 본 노트가 제거 대상) - `Handle Existing Account` (수동 confirm 서브플로우) - [ ] Authentication → Flows → "First Broker Login" 복제 — 등급: `planned` - 기본 flow는 read-only → `Copy`로 사본 생성 후 편집 - [ ] "Automatically Link Existing Account" 단계 제거 또는 `DISABLED` — 등급: `planned` - 해당 step의 requirement를 `DISABLED`로 설정 - [ ] "Confirm Link Existing Account" 단계 `REQUIRED` 활성화 — 등급: `planned` - 사용자에게 기존 계정 link 여부 확인 페이지 표시 - 이후 `Verify Existing Account by Re-authentication` step에서 password 입력 - [ ] Identity Provider 설정에서 변경된 flow를 `First Login Flow Override`로 지정 — 등급: `planned` - [ ] Review Profile flow 정책 결정 — 등급: `documented-only` - Google이 `email` / `name` / `picture` 제공 → 신규 사용자 확인 페이지 불필요한 경우 OFF - GDPR 등 동의 페이지 필요한 경우 ON - [ ] `email_verified=false` silent auto-link 차단 정책 — 등급: `documented-only` - 현재 채택 범위는 D1+D2의 소유 증명 없는 자동 link 차단까지다. - flow 진입 즉시 link/생성을 모두 거부하는 hard-reject는 구현된 custom SPI artifact가 없으므로 본 branch에서 보장하지 않는다(별도 SPI variant로 유보). > ⚠️ **2026-07-16 조사 정정 (위 항목 전제 수정)**: (a) TODO 3 "기본에서 auto-link 제거" 는 부정확 — OOTB 기본은 이미 Confirm Link 이고 AutoLink 는 별도 opt-in(`KC-FBLVERIFY-C4/C5`). 재현 시 *추가* 후 *제거*로 재구성(§구현 가이드). (b) TODO 4 "Verify Existing Account by Re-authentication REQUIRED" 는 부정확 — SMTP 설정 realm 은 "Verify Existing Account By Email"(`ALTERNATIVE`)이 기본, 재인증 관철은 email authenticator 명시 DISABLE 필요(`KC-FBLVERIFY-C1/C2/C3`). 원 TODO 는 verbatim 보존, 정정은 §Decision Evidence Map Open Risk + §Audit 를 따른다. ## 진행 중 메모 - Keycloak 공식 문서가 명시적으로 경고: **"automatic linking by email = potential security hole"** ([[raw/official-docs/keycloak-first-login-flow]] `KC-FLF-C2`); AutoLink authenticator 별도 WARNING ([[raw/official-docs/keycloak-first-broker-login-verify-authenticators-official]] `KC-FBLVERIFY-C4`). - "Confirm Link Existing Account" flow는 사용자 UX에 한 단계 추가됨 (link 확인 페이지 + password 재입력 또는 email 확인) — 보안 trade-off로 수용. - 신규 사용자 (기존 Keycloak 계정 없음) 흐름은 변경 없음: `Create User If Unique` → 신규 user 생성 → (선택) Review Profile. - 본 flow 변경은 **Google IdP에만 적용** 가능 (IdP별 First Login Flow Override 지원). 다른 IdP에 다른 flow 적용 가능. ## 결정 사항 > 추후 면접/회고에서 "왜 이렇게 했나" 답할 근거. **원 결정문 verbatim 보존** — 조사 정정은 §Decision Evidence Map Open Risk + §Audit & Findings (CLAUDE.md §11: 사용자 결정 자동 rewrite 금지, 정합 권고만). - 2026-05-25: 기본 First Broker Login Flow의 `Automatically Link Existing Account` step을 비활성화 (`DISABLED`). 보안 위험 회피. - 2026-05-25: `Confirm Link Existing Account` + `Verify Existing Account by Re-authentication` step을 `REQUIRED`로 활성화. 사용자가 기존 계정 password를 입력해야 link 완료. - 2026-05-25: Review Profile flow는 `OFF` 권장 (Google이 profile 제공). 단, 동의 페이지 비즈니스 요건 있을 시 `ON`. - 2026-05-25 (historical, superseded): ~~Google `email_verified=false`인 사용자는 link 시도 자체를 차단 (custom authenticator 또는 mapper로 강제 검증).~~ - 2026-07-18: 현재 채택 정책은 **silent auto-link 방지**다. `email_verified=false` 전체 hard-reject는 custom SPI가 실제 구현·검증된 별도 variant에서만 활성화한다. ## 결정-근거 매핑 > 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시한다. > `Decision ID` 는 이 branch-note 안에서 안정적으로 유지한다 — 형제 [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] 가 본 branch 의 `D1`/`D2`/`D4` 를 참조하므로 ID 불변. > `Supporting Claims` 는 `raw//.md#` 형식. `선택 조건`(R2): 이 조건일 때 이 결정 / 다른 조건이면 어떤 대안. | Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk | |---|---|---|---|---|---| | D1 | `Automatically Set Existing User`(AutoLink) **미사용 / DISABLED** — 기본 `Handle Existing Account`(Confirm Link) 경로 유지. **정정**: OOTB 기본은 이미 Confirm Link, AutoLink 는 별도 opt-in dangerous authenticator | 운영 flow 는 항상 이 결정 — 사용자가 임의 username/email 로 자체 등록 가능한 환경에서 AutoLink 는 공식 위험(`KC-FBLVERIFY-C4`). 대안(AutoLink 사용)은 관리자가 등록을 엄격히 curating + username/email 을 배정하는 환경에서만. 함정 *재현* 시에만 AutoLink 명시 추가(§구현 가이드 §1) | `raw/official-docs/keycloak-first-login-flow.md#KC-FLF-C2` (auto-link = potential security hole), `raw/official-docs/keycloak-first-broker-login-verify-authenticators-official.md#KC-FBLVERIFY-C4` (AutoLink WARNING), `#KC-FBLVERIFY-C5` (OOTB 충돌감지 key = email/username) | `official-vendor-doc` | 재현용 AutoLink authenticator 의 정확한 명칭/추가 위치는 admin UI 확인 필요(Claims To Verify #1). 코드 repo 부재 → `planned` | | D2 | 기존 Keycloak local 계정에 Google federated identity link 시 → `Confirm Link Existing Account` + `Verify Existing Account` 강제. **정정**: SMTP 설정 realm 은 "Verify Existing Account By Email"(`ALTERNATIVE`)이 기본 — password 재인증을 관철하려면 admin 이 email authenticator 를 명시 DISABLE | security-first(secret 소유 증명 필요) → email authenticator DISABLE → Re-authentication. SMTP 미설정 realm → Re-authentication 자동 폴백. 소비자 서비스(마찰·지원부담 최소) → Email 검증 기본값 유지 가능(단 secret 미증명) | `raw/official-docs/keycloak-first-login-flow.md#KC-FLF-C3` (info page review vs link 선택), `raw/official-docs/keycloak-first-broker-login-verify-authenticators-official.md#KC-FBLVERIFY-C1` (Email=SMTP 시 기본), `#KC-FBLVERIFY-C2` (재인증 관철=email DISABLE), `#KC-FBLVERIFY-C3` (Re-auth=폴백); `raw/company-tech-blogs/keycloak-google-login-codemancers.md` (사례 corroboration) | `official-vendor-doc` (+ `company-case-study`) | Google-first 가입(비밀번호 미설정) 사용자는 Re-authentication 으로 재인증 수단 없어 lockout 가능 — 사용자 population 조사 필요(`needs-confirmation`) | | D3 | `Review Profile` = `Off` 권장 (Google 이 profile 제공). 비즈니스 동의 요건 시 `On` | Google 이 email/name 제공(profile scope) → `Off`. mandatory 정보(email/first/last name) 미제공 IdP → `missing`. GDPR 등 동의 페이지 필요 → `On` | `raw/official-docs/keycloak-first-login-flow.md#KC-FLF-C4` (3모드 On/missing/Off 정의), `raw/official-docs/google-openid-connect-oidc.md#GOIDC-C4` (`email` claim = `email` scope 시 제공) | `official-vendor-doc` | Google `profile` scope 가 first/last name 을 *항상* 채우는지는 GOIDC 인용 범위 밖 — dev 확인 필요 | | D4 | `email_verified=false` 계정의 **silent auto-link 차단** — **core = D1(AutoLink 미사용) + D2(Confirm Link 소유증명)** (이것만으로 성립), `trustEmail=false` 는 **defense-in-depth**(위임). 원 결정의 "전용 custom authenticator/mapper hard-reject" 는 현재 구현 artifact가 없어 `OUT_OF_BRANCH_SCOPE`(hub §5 Deferred) | core 는 항상 이 결정 — 공격자가 소유 증명 없이는 link 불가(Confirm Link). `email_verified=false` 를 flow 진입 즉시 *hard-reject* 하려면 구현·검증된 커스텀 SPI authenticator가 필요 → 별도 variant | `raw/official-docs/keycloak-first-login-flow.md#KC-FLF-C2`, `raw/official-docs/keycloak-first-broker-login-verify-authenticators-official.md#KC-FBLVERIFY-C4` (+ defense-in-depth 위임 [[raw/branch-notes/feature-keycloak-idp-brokering-google-client]] D6) | `official-vendor-doc` (core 조합) | **core 차단(D1+D2)은 `trustEmail`·`email_verified` 검증과 무관하게 성립** — fix E2E 는 D6 에 blocking 아님. `trustEmail=false` 값 선택은 owner D6에 공식 근거가 있으나 runtime은 `needs-confirmation`; 전제 "Google `email_verified=false` 발급"(Claims To Verify #4)도 부가 방어 강화용으로만 필요 | ## 구현 가이드 > *결정(Decisions)* 이 "*무엇*" 이라면 본 §는 "*어디에 어떻게*" 의 사전 명세 — done-bar(재현→차단 E2E)를 다음 구현자가 되묻지 않고 수행할 수준으로. anchor 는 **공식 문서가 규정하는 authenticator 명칭·트리거 조건**. 코드 repo(`/home/donghyeon/workspace/keycloak-patterns/`)가 아직 없으므로 구현 detail 은 `planned`, admin UI 로만 확인 가능한 값은 `UNSUPPORTED_IMPL_DECISION`. > 본 branch owned 구현 대상은 **flow *구성*(D1~D4)** 뿐. `trustEmail` 값·Google client 등록은 [[raw/branch-notes/feature-keycloak-idp-brokering-google-client]] 위임, linking key(sub)는 [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] 위임 — 본 §는 *정책 참조*만. ### 1. 함정 재현 (Reproduce) — email-match auto-linking 계정탈취 > **Trace**: done-bar(hub §8.0 그룹 5) + D1 + `KC-FBLVERIFY-C4`/`C5` + `KC-FLF-C2`. > > **재현의 정확한 벡터 (2026-07-16 depth-audit Finding 2 반영)**: AutoLink 는 **email/username *값* 매칭**으로 link 하며 `email_verified` 를 트리거로 보지 않는다(`KC-FBLVERIFY-C5`). 따라서 재현의 필수 조건은 "공격자 토큰의 `email` = victim 의 email" 이고, `email_verified=false` 는 *그 email 을 신뢰하면 안 되는 이유*(unverified 소유 주장)일 뿐 AutoLink 발동 조건이 아니다. `email_verified` 자동신뢰 벡터는 §2 의 `trustEmail` 방어(위임 D6) 관심사로 분리 — 즉 본 재현은 `email_verified` 미검증(Claims To Verify #4)에 **의존하지 않는다**. > > - **UNSUPPORTED_IMPL_DECISION (재현 harness)**: 실제 Google 로는 *내가 소유하지 않은* `victim@example.com` 토큰을 발급받을 수 없어 real Google 로는 `before(탈취 성공)` 관측 불가. 재현은 **claim 을 제어 가능한 OIDC OP** 로 수행 — (a) 로컬 스택에 2번째 Keycloak realm 을 mock OP 로 세워 main realm 에 외부 OIDC IdP 로 등록하고 그 OP 사용자 `email=victim@example.com` 설정, 또는 (b) 경량 mock-oidc OP 로 임의 `email`/`email_verified` claim emit. trade-off: (a) Keycloak 자족(추가 realm 운영) vs (b) 경량(별도 컨테이너) — 착수 시 (a) 권장. AutoLink authenticator 의 정확한 UI 명칭("Automatically Set Existing User" vs "Automatically Link Existing Account")·requirement·위치도 admin UI 확정(Claims To Verify #1). 코드 repo 부재 → `planned`. | 단계 | 명세 | 근거 | |---|---|---| | 전제(dep) | Google IdP 대신 **제어 가능한 OIDC OP**(2nd Keycloak realm 또는 mock OP)를 main realm 에 외부 IdP 로 등록 — 공격자가 `email` claim 을 통제해야 함 | done-bar, depth-audit Finding 1 | | 피해자 셋업 | main realm 에 local user `victim@example.com`(password 설정) 존재 | done-bar | | 취약 flow | First Broker Login 사본에 AutoLink(`Automatically Set Existing User`) authenticator 추가 → **email 값 매칭**으로 무확인 link | `KC-FBLVERIFY-C4` (WARNING), `KC-FBLVERIFY-C5` (email/username 매칭) | | 공격 | 제어 OP 사용자 `email=victim@example.com`(`email_verified=false` = 신뢰불가 email 표현이나 AutoLink 트리거 아님) → 그 IdP 로 로그인 → AutoLink 가 email 값으로 victim 계정에 연결 | `KC-FLF-C2` (위협), 목표/WHY 시나리오 | | 관측(before) | 공격자 세션이 victim 의 계정/roles 보유 — 탈취 성공 로그/스크린샷 | done-bar | ### 2. 차단 (Fix) — Confirm Link Existing Account > **Trace**: D1 + D2 + `KC-FLF-C3` + `KC-FBLVERIFY-C1`/`C2`/`C3`. > > - **UNSUPPORTED_IMPL_DECISION**: Email vs Re-authentication 중 무엇을 강제할지의 realm-level 선택(SMTP 유무)은 배포 realm 사실 — 본 branch 는 *정책*(secret 증명 우선 시 email DISABLE)만. trade-off: SMTP 미설정 학습 realm 은 Re-auth 가 자동 폴백이므로 별도 조치 불필요. | 단계 | 명세 | 근거 | |---|---|---| | flow 복원 | AutoLink 제거 → 기본 `Handle Existing Account`(= `Confirm Link Existing Account` + `Verify Existing Account`) 유지 | D1, `KC-FLF-C3` | | 재인증 관철(선택) | password 소유 증명 강제 시: "Verify Existing Account By Email" **DISABLE** → "Verify Existing Account By Re-authentication" 실행 | `KC-FBLVERIFY-C2`, `KC-FBLVERIFY-C3` | | 기본 동작 주의 | SMTP 설정 realm 은 email 확인이 기본(`ALTERNATIVE`) — secret 미증명이라도 소유 확인은 됨 | `KC-FBLVERIFY-C1` | | email_verified 보강 (defense-in-depth, 위임) | `trustEmail=false` 로 IdP email 을 무조건 verified 처리하지 않음. **단 core 차단(Confirm Link 소유증명)은 `trustEmail` 값과 무관하게 성립** — 본 fix E2E 는 D6 에 blocking 되지 않는 부가 방어 | 위임 [[raw/branch-notes/feature-keycloak-idp-brokering-google-client]] D6 (자체 `UNSUPPORTED` — 근거 확정은 그 branch) | | 재공격 | 공격자(제어 OP) 로그인 → Confirm Link info page → email 확인/password 재인증 요구 → 소유 증명 실패 → **link 차단** | D2, `KC-FLF-C3` | | 관측(after) | before(탈취 성공) vs after(차단) 대조 기록 → `locally-verified` 승급 근거 | done-bar | ### 3. Review Profile 모드 — D3 > **Trace**: D3 + `KC-FLF-C4`. > > - **UNSUPPORTED_IMPL_DECISION**: Google `profile` scope 가 first/last name 을 항상 채우는지 미확인(Claims To Verify #? — GOIDC 범위 밖). trade-off: 미충족 시 `missing` 모드가 안전(누락 시에만 프로필 페이지). | 모드 | 조건 | 근거 | |---|---|---| | `Off` (권장) | Google 이 email/name 제공 → 프로필 페이지 불필요 | `KC-FLF-C4`, `GOIDC-C4` | | `missing` | mandatory(email/first/last name) 미제공 IdP → 누락 시만 표시 | `KC-FLF-C4` | | `On` | GDPR 등 동의/추가정보 수집 요건 | `KC-FLF-C4` | ## 엣지·실패·의존 > R4 캡처용. 정상 경로 외에 *구현 중 부딪힐* 실패/엣지/다른 계약 의존을 미리 열거. - **실패·엣지 경로**: - **Google-first 가입 lockout (D2)**: 비밀번호 미설정(Google 으로만 가입) 사용자가 다른 IdP/계정 link 충돌 시 "Verify Existing Account By Re-authentication" 로 넣을 password 가 없어 진행 불가 — email 폴백 또는 대안 경로 설계 필요(`needs-confirmation`). - **SMTP 미설정 realm (D2)**: "Verify Existing Account By Email"(기본 `ALTERNATIVE`) 사용 불가 → 자동으로 "Verify Existing Account By Re-authentication" 폴백(`KC-FBLVERIFY-C3`). 학습 스택은 SMTP 없이 시작하므로 기본이 Re-auth 임에 유의. - **OOTB 충돌감지 = email/username (D1 tension)**: `Create User If Unique` 는 IdP `sub` 가 아니라 email/username 으로 충돌 감지(`KC-FBLVERIFY-C5`). sub-only 매칭·`email_verified` hard-reject 를 원하면 커스텀 SPI authenticator 필요 → 본 branch 범위 밖(§Audit `OUT_OF_BRANCH_SCOPE`, hub §5 Deferred). - **flow 오설정 시 Google IdP 전면 차단**: 기본 flow 는 read-only — 반드시 복제 후 편집. 잘못 편집하면 해당 IdP 로그인 전체가 막힘(진행 중 메모). - **재현용 취약 구성 잔존 위험**: 함정 재현(§1) 후 AutoLink authenticator 를 제거하지 않으면 실제 취약점이 남음 — 차단(§2) 단계에서 반드시 원복 확인. - **다른 계약 의존**: - [[raw/branch-notes/feature-keycloak-idp-brokering-google-client]] `D6`(`trustEmail=false`) + `D1`~`D5`(Google client 등록·discovery·redirect URI) — 본 flow 의 **전제**. IdP 미등록이면 First Broker Login 자체가 트리거되지 않음. `trustEmail` 값이 바뀌면 D4 방어 전제도 변함. - [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] `D1`(sub 기반 linking key) — 본 flow 는 sub-vs-email 의 linking 정책과 정합해야. 그 branch 는 flow *구성* 을 본 branch `D1`/`D2`/`D4` 에 위임(역방향 의존). - [[raw/branch-notes/feature-keycloak-google-claim-attribute-mapping]] `D3`(Sync Mode)/`D4`(email Attribute Importer) — first-login 시 매핑되는 attribute owner. 매핑이 바뀌면 §Review Profile 입력값도 바뀜. - [[raw/branch-notes/feature-keycloak-account-linking-spa-ux]] — Confirm Link info page 노출 시 SPA redirect/return UX. 본 flow 가 확인 페이지를 트리거하면 그 branch 가 UX 를 consume. ## 검증해야 할 주장 > 공식 문서 근거가 있어도 내 프로젝트/버전에서의 동작을 자동으로 보장하지 않는다. | Claim | Why uncertain | How to verify | Status | |---|---|---|---| | OOTB Default First Broker Login flow 는 auto-link 를 *포함하지 않으며*(`Create User If Unique`→`Handle Existing Account`=Confirm Link 가 기본), 함정 재현엔 `Automatically Set Existing User`(AutoLink) authenticator 를 명시 추가해야 한다 — 그 정확한 명칭/requirement/추가 위치 | `KC-FBLVERIFY-C4/C5` 로 방향은 확정됐으나 배포 버전 admin UI 의 정확한 authenticator 라벨·토글은 미확인 (기존 노트의 "기본이 auto-link" 전제는 §Audit `CLAIM_DRIFT` 로 정정) | Keycloak admin console > Authentication > Flows > First Broker Login 복제 후 step 캡처 + AutoLink 추가 시연 | `needs-confirmation` | | `Confirm Link Existing Account` 와 `Verify Existing Account`(By Email / By Re-authentication) 의 requirement 조합이 admin UI 에서 의도대로 설정 가능 | `KC-FLF-C3`/`KC-FBLVERIFY-C1~C3` 는 트리거 조건만; 실제 UI step list·설정 가능성은 별도 | dev Keycloak flow editor 에서 step list + email authenticator DISABLE 시연 | `needs-confirmation` | | Identity Provider 의 "First Login Flow Override" 가 IdP 별로 다른 flow 지정 가능 | 본 branch Sources 에 Override 메커니즘 verbatim 없음(개요만) | Admin console > Identity Providers > Google > Advanced Settings 캡처 또는 admin guide 추가 인용 | `needs-confirmation` | | Google 이 일부 시나리오에서 `email_verified=false` ID token 발급 가능 (D4 전제) | `GOIDC-C4` 는 `email` claim 만; `email_verified` semantics 는 명시적 범위 밖 | `raw/official-docs/google-openid-connect-oidc.md` claims table 의 `email_verified` 행 추가 발췌 또는 dev Google 계정으로 재현 | `needs-confirmation` | | `email_verified=false` hard-reject 를 원하면 커스텀 SPI authenticator 가 필요하다 (D4 OUT_OF_BRANCH_SCOPE 근거) | OOTB 는 email/username 매칭(`KC-FBLVERIFY-C5`), `email_verified` 조건부 거부 built-in 여부 미확인 | Keycloak Identity Provider Mappers / First Broker Login SPI 문서 확인 + dev 재현 | `needs-confirmation` | | 인용한 authenticator 명칭·기본 등급(`ALTERNATIVE`)이 배포 예정 Keycloak **release tag** 에서도 동일 | 근거 raw(`first-login-flow.adoc`)는 keycloak `main` branch 기준(`KC-FBLVERIFY` 버전 caveat) | 배포 버전 tag 의 admin guide / Admin Console 재확인 | `needs-confirmation` | ## Audit & Findings > 2026-07-16 `/branch-spec` 채움(기존 corpus 정독 — web 조사 불요, 모든 결정 근거는 이미 raw 에 존재) 결과. 사용자 작성 결정은 verbatim 보존, 아래는 정합 권고·정정만 (CLAUDE.md §11). - **FRAMING_DRIFT (Should-fix, hub 정합)**: 노트 원 프레이밍 "P1B sub-sub-branch 는 전체 `documented-only` / 실 구현 안 함 / wiki 추출 안 함" 은 hub 2026-07-14 재편(§8.0 그룹 5 + §1 성공기준 + §10 Phase 2/3)으로 **stale**. 현재 이 branch 는 Google 그룹 Tier-2 **구현** branch(done-bar = 재현→차단, 목표 `locally-verified`). 헤더·범위·Closure 를 구현 branch 로 갱신, 원 결정문/TODO 는 verbatim 보존. - **CLAIM_DRIFT (D1, 정정)**: 원 전제 "기본 First Broker Login Flow 가 `Automatically Link Existing Account` 를 *포함*, 이를 *제거*" 는 부정확. OOTB 기본은 `Create User If Unique`(충돌감지 key = **email/username** `KC-FBLVERIFY-C5`) → `Handle Existing Account`(Confirm Link) 이고, email *자동* link 는 별도 opt-in `Automatically Set Existing User`(공식 WARNING `KC-FBLVERIFY-C4`). done-bar 는 "제거"가 아니라 "재현 위해 *추가* → 기본으로 *복원*"(§구현 가이드 §1→§2). - **CORRECTION (D2, 정정)**: 원 "Verify Existing Account by Re-authentication `REQUIRED`(기본)" 은 부정확 — SMTP 설정 realm 은 "Verify Existing Account By Email"(`ALTERNATIVE`)이 기본(`KC-FBLVERIFY-C1`). password 재인증 관철엔 admin 이 email authenticator 를 **명시 DISABLE** 필요(`KC-FBLVERIFY-C2`); Re-auth 는 email 사용 불가 시 폴백(`KC-FBLVERIFY-C3`). (동일 정정이 형제 [[raw/branch-notes/feature-keycloak-account-linking-sub-vs-email]] §Audit `CORRECTION(D2)` 에도 존재 — 본 branch 가 flow *구성* owner 이므로 정정의 canonical 위치는 여기.) - **RESCOPE (D4, UNSUPPORTED → 조합 근거 + OUT_OF_BRANCH_SCOPE)**: 원 D4 "custom authenticator/mapper 로 `email_verified=false` 강제 차단" 은 그 자체로 근거 부재(`UNSUPPORTED_DECISION`)였다. 실무 방어는 **D1(AutoLink 미사용) + D2(Confirm Link 소유증명) + 위임 `trustEmail=false`(idp-brokering-google-client D6)** 의 *조합*으로 이미 성립(공격자가 소유 증명 없이 link 불가) → 조합 근거로 grounded. flow 진입 즉시 *hard-reject* 하는 전용 custom SPI authenticator 는 **hub §5 Deferred(server-side SPI 트랙)** 으로 `OUT_OF_BRANCH_SCOPE`. 전제(Google `email_verified=false` 발급)는 Claims To Verify #4. - **OUT_OF_BRANCH_SCOPE (이관 권고)**: `sub` 기반 충돌감지 전용 authenticator + `email_verified` hard-reject SPI 는 본 branch(flow 구성) 범위 밖 → hub §5 Deferred SPI 트랙 또는 별도 branch(예: `feature-keycloak-firstlogin-emailverified-authenticator`). 형제 sub-vs-email 도 동일 gap 을 §Audit `OUT_OF_BRANCH_SCOPE` 로 이관 권고 중 — 중복 신설 금지, 단일 SPI branch 로 수렴 권고. - **NO_GROUND_TRUTH (한계 명시)**: 코드 repo `/home/donghyeon/workspace/keycloak-patterns/` 미생성(hub §9). `actually-implemented` 주장 불가 — 모든 구현 detail 은 `planned`/`needs-confirmation`. §참조의 ca-tmpl ground truth(registries/error-codes 등)는 **본 프로젝트와 무관**(keycloak-patterns 는 별도 repo) — 계약값 검증 대상 아님. - **BIDIR_LINK (fix 적용)**: [[raw/official-docs/keycloak-first-broker-login-verify-authenticators-official]] 의 Parent 표·`related_branches` 에 본 branch 가 누락돼 있었음(sub-vs-email 만). 본 `/branch-spec` 에서 backlink 추가(양방향 링크 정합, `rules/linking-rules`). - **STALE_SUMMARY 전파 (fix 적용)**: [[raw/branch-notes/feature-keycloak-account-linking-spa-ux]] 가 본 branch D2 를 "Confirm Link + Re-auth `REQUIRED`" 로 요약(L116·L181)했으나 KC-FBLVERIFY 정정(Email 기본 / Re-auth 폴백)과 어긋남 → 같은 세션에서 두 참조를 corrected pointer 로 갱신(consistency-contract §전파). - **DEPTH_LOOP_1 (2026-07-16 depth-audit 반영, §8c 루프 1회)**: `branch-depth-auditor` 가 Blocking 1 + Should-fix 2 를 반환 → 다음 정정: (1) **REPRODUCE_HARNESS (Blocking F1)** — real Google 로는 미소유 email 토큰 발급 불가로 `before(탈취)` 관측 불가 → §구현 가이드 §1 에 **제어 가능 OIDC OP(2nd Keycloak realm / mock OP)** harness 를 `UNSUPPORTED_IMPL_DECISION` + trade-off 로 명세. (2) **VECTOR_SEPARATION (F2)** — AutoLink 는 `email` *값* 매칭이지 `email_verified` 트리거 아님(`KC-FBLVERIFY-C5`) → §1 에 두 벡터 분리 명시, 재현은 Claims To Verify #4(email_verified 발급)에 비의존. (3) **TRUSTEMAIL_DECOUPLE (F3)** — 위임 owner [[raw/branch-notes/feature-keycloak-idp-brokering-google-client]] D6 의 값 선택 근거가 이후 공식 문서로 보강됐지만, core 차단(D1+D2 Confirm Link)은 여전히 `trustEmail` 과 무관하게 성립하고 `trustEmail=false` 는 defense-in-depth 다(fix E2E 가 D6 에 blocking 아님). Advisory F4(Review Profile/version hedge)는 이미 정직 헷지 — 조치 불요. ## 마주친 문제 - 아직 없음(문서 단계 — 코드 repo 생성 시 재현/차단 시연에서 발생 예상). ## 묶음 - [[raw/official-docs/google-oidc-discovery-spec]] - [[raw/official-docs/keycloak-first-broker-login-flow]] - [[raw/official-docs/keycloak-first-broker-login-verify-authenticators-official]] - [[raw/official-docs/keycloak-first-login-flow]] - [[raw/official-docs/keycloak-identity-brokering-overview-official]] > 본 sub-sub-branch 는 leaf — 자식 자료 없음. Phase 3 실 구현 또는 외부 산출물 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화. ### 근거 자료 - [[raw/official-docs/keycloak-first-login-flow]] — D1/D2/D3 (auto-link 경고 + Confirm Link info page + Review Profile 모드) - [[raw/official-docs/keycloak-first-broker-login-verify-authenticators-official]] — D1/D2 (AutoLink WARNING + Verify authenticators 트리거 조건) - [[raw/official-docs/google-openid-connect-oidc]] — D3/D4 전제 (email scope / email_verified 범위) - [[raw/company-tech-blogs/keycloak-google-login-codemancers]] — D2 corroboration (실무 사례) - [[raw/official-docs/keycloak-identity-brokering-overview-official]] — First Login Flow Override 배경 ### 오류 기록 (이 sub-sub-branch 작업 중 발생) - (없음 — 현재 문서 단계) ### 면접 준비 (이 작업에서 나올 수 있는 면접 질문) - (없음 — 실 구현 단계에 누적) "왜 email auto-linking 이 계정탈취인가 / Confirm Link 가 어떻게 막나 / OOTB 기본 flow 는 이미 안전한가?" 후보. ## 관련 일일 노트 ## 완료 후 정리 - PR 링크: (없음 — 코드 repo 미생성) - 리뷰 메모: - 머지 결과 / 배포 환경: 없음 (현재 문서 단계; done-bar 달성 시 `locally-verified` = `docker compose up` 로컬 재현→차단) - **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출): done-bar(재현→차단) `locally-verified` 달성 후 hub §11 정책에 따라 Phase 4 시점 검토. 현재 없음. - **추출하지 않을 항목** (planned / documented-only): 현 시점 전체 (`planned`/`documented-only`).