Files
llm-wiki/raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce.md

28 KiB
Raw Permalink Blame History

title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, branch, parent_branch, related_projects, tags, created, target_merge, status_label, contract_packet_sha256
title source_type status id kind project work_item inherits refines overrides depends_on contract_packet branch parent_branch related_projects tags created target_merge status_label contract_packet_sha256
branch / feature-keycloak-vanilla-js-spa-pkce (vanilla JS SPA — Authorization Code + PKCE) branch-note raw BR-KEYCLOAK-PATTERNS-OVERVIEW-003 project-work-item keycloak-patterns-overview WI-KEYCLOAK-PATTERNS-OVERVIEW-003
DEC-KEYCLOAK-PATTERNS-OVERVIEW-SPA-CLIENT-001@1
DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1
WI-KEYCLOAK-PATTERNS-OVERVIEW-002
1 feature-keycloak-vanilla-js-spa-pkce
keycloak-patterns
branch
keycloak-patterns
p3a
implementation
vanilla-js
spa
pkce
oidc-client-ts
2026-05-25 in-progress bb1be862636aa363e6d10eb54600075ab82106f6c707a98acbfd84a938adf0f4

branch: feature-keycloak-vanilla-js-spa-pkce (vanilla JS SPA — Authorization Code + PKCE)

Layer: raw/branch-notes/raw/project-notes/keycloak-patterns-overviewWI-KEYCLOAK-PATTERNS-OVERVIEW-003 직접 branch. P3A는 실 구현 대상. 본 sub-sub는 학습 + 작업 plan 기록 — 실 구현은 /home/donghyeon/workspace/keycloak-patterns/.

부모 (필수)

raw/project-notes/keycloak-patterns-overview

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 1
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: vanilla JS PKCE login·token 수령·protected API 200이 재현된다

상속한 프로젝트 결정

Decision Ref Project Summary Branch Application Source
DEC-KEYCLOAK-PATTERNS-OVERVIEW-SPA-CLIENT-001@1 AP1은 public client와 Authorization Code + PKCE를 사용한다 vanilla JS SPA의 Authorization Code + PKCE flow에 적용한다 raw/project-notes/keycloak-patterns-overview
DEC-KEYCLOAK-PATTERNS-OVERVIEW-ACCEPTANCE-001@1 done-bar는 E2E success와 signature security failure 재현·해결 evidence다 login·token 수령·protected API 200을 E2E 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

없음.

목표

vanilla JS (no React/Vue/Angular)로 OIDC Authorization Code + PKCE 흐름을 직접 구현한다. oidc-client-ts 우선 채택 후, 별도 학습 단계에서 manual crypto.subtle 기반 PKCE 비교 구현. login button → Keycloak redirect → callback → token storage → /api/me 호출 → silent renew → logout 전체 lifecycle.

면접 질문: "PKCE 흐름을 코드로 설명해 주세요." → "SPA가 code_verifier 43128자 랜덤 생성, code_challenge = BASE64URL(SHA256(code_verifier))로 변환합니다. authorize 요청에 code_challengecode_challenge_method=S256을 첨부하고, 콜백에서 받은 code로 token 교환할 때 원본 code_verifier를 함께 보냅니다. authorization code interception attack 방어 — public client는 client_secret이 없으므로 PKCE가 사실상 필수입니다."

  • 이슈:
  • PR: (별도 keycloak-patterns repo)

범위

포함 범위

  • index.html (login button, logout button, /api/me 호출 결과 표시 영역)
  • app.js (oidc-client-ts UserManager 사용)
  • callback.html (redirect callback 처리 페이지) — 또는 main page에서 ?code=... 감지
  • PKCE S256 (oidc-client-ts 내부 처리)
  • token storage: in-memory (학습용, UserManager.events.addUserLoaded(...)로 closure 보관)
  • silent renew (automaticSilentRenew: true)
  • Authorization: Bearer ${user.access_token} 헤더로 /api/me 호출
  • logout button → signoutRedirect() (Keycloak /logout endpoint)
  • (별도 단계) manual PKCE: crypto.subtle.digest('SHA-256', ...) + base64url encoding 직접 구현

제외 범위

  • React/Vue/Angular framework 사용 (vanilla 학습 목적)
  • iframe 기반 silent SSO (deprecated, 대신 refresh token 사용)
  • 자체 token storage 암호화
  • mobile / native client (PKCE 자체는 동일, 본 sub는 SPA)

근거 (필수, 최소 1개+)

TODO

  • npm init + oidc-client-ts 설치 — 등급: planned
  • index.html: login button (id=login), logout button (id=logout), result 영역 (id=result) — 등급: planned
  • app.js: UserManager 인스턴스 — 등급: planned
    • authority: 'http://localhost:8080/realms/keycloak-patterns'
    • client_id: 'spa-client'
    • redirect_uri: 'http://localhost/callback.html'
    • post_logout_redirect_uri: 'http://localhost/'
    • response_type: 'code'
    • scope: 'openid profile'
    • automaticSilentRenew: true
  • login button click → userManager.signinRedirect() — 등급: planned
  • callback.html: <script>new UserManager(config).signinRedirectCallback().then(user => location.href='/') — 등급: planned
  • main page load 시 userManager.getUser() → memory user가 있으면 runtime backend URL로 API 호출, reload로 없으면 재인증 — 등급: planned
  • fetch('http://localhost:8081/api/me', { headers: { Authorization: 'Bearer ' + user.access_token } }) 또는 동일 값을 주입한 runtimeConfig.backendBaseUrl 사용 → JSON render — 등급: planned
  • logout button click → userManager.signoutRedirect() — 등급: planned
  • silent renew 검증: access token 만료 (5분) 직전 자동 갱신 발생 → DevTools Network 탭에서 /token (grant_type=refresh_token) 호출 확인 — 등급: planned
  • CORS 검증: nginx 80 → backend 8081 호출 시 preflight 통과 — 등급: planned
  • (별도 단계) manual PKCE 구현: — 등급: planned
    • crypto.getRandomValues(new Uint8Array(32)) → base64url → code_verifier
    • crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier)) → base64url → code_challenge
    • sessionStorage.setItem('pkce_verifier', verifier)
    • manual token exchange는 OIDC discovery의 token_endpoint absolute URL 사용 (grant_type=authorization_code + code_verifier)
  • (별도 단계) oidc-client-ts vs manual 동작 비교 — 등급: planned

진행 중 메모

  • oidc-client-ts 우선 채택 이유: production-grade silent renew / state / nonce / token validation을 한 줄로 처리. 학습 후 manual로 내부 동작 검증.
  • token storage: pinned oidc-client-ts version의 stateStore/userStore default는 아직 미검증이다. default에 의존하지 않고 token/user store는 explicit in-memory로 선택한다. redirect transaction state만 full-page callback 생존을 위해 explicit sessionStorage에 두고 callback 직후 정리한다.
  • redirect_uri: http://localhost/callback.html. Keycloak client Valid Redirect URIs에 정확히 등록되어야 함 (sub-5-2 참조).
  • silent renew: refresh token rotation ON이면 매 갱신마다 새 refresh token. rotation 동작 검증은 sub-5-6에서.
  • scope=openid profile: openid는 OIDC 식별, profilepreferred_username 등 user claim 포함.
  • manual PKCE 학습 가치: code_challenge 계산, state/nonce 관리, callback URL parsing을 직접 다뤄야 OIDC 흐름이 머리에 그려짐.

결정 사항 (decisions)

  • 2026-05-25: oidc-client-ts 우선, manual은 별도 단계. 이유: 작동하는 환경을 먼저 만들고 내부 동작은 비교 학습.
  • 2026-05-25: token storage in-memory (학습용). 이유: localStorage XSS 우려 — prod에서는 BFF 패턴이 더 안전. 학습 단계에서 token 흐름이 명확히 보이도록 in-memory 채택.
  • 2026-05-25: automaticSilentRenew: true. 이유: refresh token rotation 동작 시연 (sub-5-6) 자동화.
  • 2026-05-25: response_type=code 고정 (legacy implicit flow 미사용). 이유: RFC 8252 / OAuth 2.1 권장 — implicit flow는 deprecated.
  • 2026-05-25: redirect_uri는 http://localhost/callback.html 단일. 이유: callback page 분리 → main page 로딩 흐름과 분리해 디버깅 쉬움.
  • 2026-07-18 (/branch-spec 자동조사 보강): D4 를 UNSUPPORTED 에서 해소 — implicit deprecation 의 공식 근거를 raw/official-docs/oauth-v2-1-draft-ietf OA21-C2(Implicit + ROPC grant 제거) + OA21-C1(PKCE MUST all clients)로 확정. 기존 RFC 8252 추정 대신 OAuth 2.1 표준 직접 인용.
  • 2026-07-18 (/branch-spec 자동조사 보강): D5 를 UNSUPPORTED 에서 해소(부분) — redirect_uri exact-match 요구는 OA21-C5(registered redirect URI 와 exact match 안 하면 MUST 거부)로 확정. 단 단일 callback page 분리 vs main-page ?code= 감지 는 표준 요구가 아닌 디버깅 편의 판단이므로 UNSUPPORTED_IMPL_DECISION(§구현가이드 2)으로 강등.
  • 2026-07-18 (/branch-spec 자동조사 보강): D2 를 UNSUPPORTED 에서 해소(위임) — token 저장 위치 trade-off 는 owner sibling raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff (D1/D2/D6, OWASP·Curity·OAuth 2.1 근거)가 소유. 본 branch 는 그 분석을 재진술하지 않고 학습 단계용 in-memory 지점을 선택(선택 조건 = 학습 vs prod). Reference-Only(rules/consistency-contract).

결정-근거 매핑

각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시한다. Decision ID 는 이 branch-note 안에서 안정적으로 유지한다. Supporting Claimsraw/<category>/<slug>.md#<CLAIM-ID> 형식. 선택 조건 열(R2): 이 조건일 때 이 결정 / 다른 조건이면 어떤 대안. 분기 없으면 N/A.

Decision ID Decision 선택 조건 (언제 이 결정 / 언제 대안) Supporting Claims Evidence Strength Open Risk
D1 oidc-client-ts 우선 채택 (manual PKCE 는 비교 학습용 별도 단계) 작동하는 baseline 을 먼저 확보하고 내부 동작을 비교 학습 → library 우선. 브라우저 내부 crypto/state/nonce 를 직접 다뤄 학습 → manual crypto.subtle 구현(§구현가이드 5) raw/official-docs/oidc-client-ts-library.md#OIDCTS-C3 (PKCE 지원 명시), raw/official-docs/oidc-client-ts-library.md#OIDCTS-C2 (OAuth 2.1 지속 지원 protocol 만) official-vendor-doc OIDCTS-C1 이 origin project 2021-06 개발 중단을 명시 — fork 의 active maintenance / 보안 패치 상태는 별도 확인
D2 pure SPA token storage = explicit in-memory (access/refresh 모두), reload 시 재인증 AP1 pure SPA baseline이면 default store에 의존하지 않고 memory-only. HttpOnly refresh cookie는 raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff D7의 TMB/BFF variant 위임: raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff D1/D2/D6 delegated (official-reference via owner) 실행 중 XSS 노출은 남는다. redirect transaction state와 token user store를 구분해야 함
D3 automaticSilentRenew: true (refresh token rotation 자동화) refresh token rotation 동작을 자동 시연하려는 학습 목표 → 활성. Keycloak cross-site + Safari 배포로 iframe silent renew 가 구조적으로 실패하는 환경 → refresh_token grant 직접 사용 우선(owner token-storage D3) raw/official-docs/oidc-client-ts-library.md#OIDCTS-C4 (Refresh Token Grant 지원), raw/official-docs/oidc-client-ts-library.md#OIDCTS-C5 (Silent Refresh Token in iframe Flow 지원). 실패 조건 위임: raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff D3(Safari)·D3a(Chrome) official-vendor-doc OIDCTS-C5 의 "Does not prove": 3rd-party cookie 차단 환경(Safari ITP / Chrome Incognito)에서 iframe flow 보장 안 함. automaticSilentRenew 가 iframe vs refresh_token grant 중 무엇을 default 로 쓰는지 미확정(Claims To Verify). rotation default 활성은 Keycloak server-side 설정 의존
D4 response_type=code 고정 (implicit flow 미사용) public client(SPA)의 표준 flow → 항상 code + PKCE. implicit 은 OAuth 2.1 에서 제거되어 대안이 아님 raw/official-docs/oauth-v2-1-draft-ietf.md#OA21-C2 (Implicit + ROPC grant 제거), raw/official-docs/oauth-v2-1-draft-ietf.md#OA21-C1 (PKCE MUST all clients), raw/official-docs/oauth2-pkce-rfc-7636.md#PKCE-RFC7636-C1 (public client + code grant → interception → PKCE 전제) official-standard (이전 UNSUPPORTED 해소 — OA21-C2 가 implicit 제거를 직접 증명) code_verifier 길이/문자셋(RFC 7636 §4.1)은 본 인용 범위 밖
D5 redirect_uri = http://localhost/callback.html 단일 (exact-match) authorization server 는 registered redirect URI 와 exact match 안 하면 MUST 거부 → 정확한 단일 URI 등록. callback 전용 page 분리 vs main page ?code= 감지 는 디버깅 편의 판단(임의) raw/official-docs/oauth-v2-1-draft-ietf.md#OA21-C5 (redirect URI exact-match MUST) official-standard (exact-match 요구); callback page 분리는 UNSUPPORTED_IMPL_DECISION(§구현가이드 2) exact-match 자체는 OA21-C5 로 증명. 단일 callback page 분리는 표준 요구 아님 — main-page handling 도 유효. 등록된 redirect URI 실체는 owner sibling raw/branch-notes/feature-keycloak-realm-client-export 소유 → 그 등록값 변경 시 D5 영향

구현 가이드

본 branch 는 documented-only(실 구현 repo keycloak-patterns/ 아직 부재 — NO_GROUND_TRUTH). 아래는 다음 구현자가 되묻지 않고 코드를 작성할 수준의 사전 명세. 각 sub-section 은 Decision ID + Supporting Claim ID 를 Trace 하거나 UNSUPPORTED_IMPL_DECISION/OUT_OF_BRANCH_SCOPE 라벨을 단다(CLAUDE.md §15.5 3-rule).

1. UserManager 설정 명세 (config object)

Trace: D1 (OIDCTS-C3 PKCE 자동), D3 (OIDCTS-C4/C5 refresh·silent), D4 (OA21-C1 PKCE MUST, OA21-C2 implicit 제거, PKCE-RFC7636-C1), D5 (OA21-C5 exact-match)

  • OUT_OF_BRANCH_SCOPE: client_id(spa-client) 값·Valid Redirect URIs 등록은 owner sibling raw/branch-notes/feature-keycloak-realm-client-export #D2(현재 학습용 http://localhost/*·http://127.0.0.1/* wildcard 등록), server-side PKCE method=S256 강제는 그 #D1(KC-PKCE-C1/C3) 소유. 본 branch 는 그 등록값을 consume 만 한다.
config key Trace 라벨
authority http://localhost:8080/realms/keycloak-patterns realm URL → .well-known/openid-configuration 자동 조회. iss 검증 위해 hostname 이 KC_HOSTNAME 과 일치 필수(OIDCTS docs 경고: authorityKC_HOSTNAME) → iss-claim-hostname-mismatch #D1 의존 consume (iss-claim #D1)
client_id spa-client client 등록 owner OUT_OF_BRANCH_SCOPE (realm-client-export #D1/#D2)
redirect_uri http://localhost/callback.html D5 / OA21-C5 (exact-match); 등록은 realm-client-export #D2
post_logout_redirect_uri http://localhost/ logout redirect UNSUPPORTED_IMPL_DECISION: 루트 / 로 복귀는 임의 — trade-off: 전용 logged-out page 분리하면 UX 명확하나 파일 1개 추가
response_type code D4 / OA21-C2(implicit 제거)·PKCE-RFC7636-C1
scope openid profile openid=OIDC 식별, profile=preferred_username claim UNSUPPORTED_IMPL_DECISION: profile 외 scope(email/roles 등)는 /api/me 요구에 따라 — trade-off: 최소 scope 원칙 vs claim 부족 시 재요청
automaticSilentRenew true D3 / OIDCTS-C4/C5

2. 페이지·이벤트 wiring 명세 (index.html

Trace: D1 (library signinRedirect/signinRedirectCallback/signoutRedirect), D5 (callback URI)

  • UNSUPPORTED_IMPL_DECISION: (a) DOM element id 명명(login/logout/result)은 임의 — trade-off: 짧은 고정 id 는 단순하나 다중 위젯 시 충돌 위험. (b) callback 전용 callback.html 분리 vs main page 에서 ?code= 감지는 D5 Open Risk 의 디버깅 편의 판단 — trade-off: 분리는 main 로딩 흐름과 격리돼 디버깅 쉽지만 redirect_uri·정적 파일 1개 추가; main-page handling 은 파일 최소이나 초기 로드 로직에 code 교환이 섞임.
대상 명세
index.html <button id="login">, <button id="logout">, <pre id="result">
app.js (main load) userManager.getUser() → user 있으면 §4 /api/me 호출; #login.onclick → userManager.signinRedirect(); #logout.onclick → userManager.signoutRedirect()
callback.html <script>new UserManager(config).signinRedirectCallback().then(() => location.href = '/') (code→token 교환 후 main 복귀)

3. explicit in-memory token store 명세

Trace: D2 (위임 raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff D1/D2 — localStorage 회피, access=memory)

pinned version default는 미검증이므로 active baseline은 default와 무관하게 explicit store를 지정한다.

  • userStore: new WebStorageStateStore({ store: new InMemoryWebStorage() }) — access/refresh/id token을 memory-only로 유지.
  • stateStore: new WebStorageStateStore({ store: window.sessionStorage }) — full-page redirect의 state/transaction만 생존시키며 callback 성공 뒤 정리. token persistence 용도가 아니다.
  • reload 뒤 getUser()가 비면 silent 복구를 기본 가정하지 않고 재인증한다.

4. /api/me 호출 + silent renew 검증 명세

Trace: D3 (silent renew), D1 (user.access_token)

  • OUT_OF_BRANCH_SCOPE: nginx 80 → backend 8081 의 CORS preflight 정책(Authorization 헤더 허용·credentials)은 backend Spring Security 결정 → RS 계열 raw/branch-notes/feature-keycloak-spring-rs-audience-validator 로 위임. 본 branch 는 "Bearer 헤더로 호출한다"는 client 측 요구만 남긴다.
  • static-only nginx/3-port topology에서는 runtimeConfig.backendBaseUrl 기본값 http://localhost:8081을 주입하고 fetch(runtimeConfig.backendBaseUrl + '/api/me', ...)로 호출한다. relative /api/me는 nginx :80로 가므로 사용하지 않는다.
  • silent renew 검증(§Claims To Verify): access token 만료 직전 DevTools Network 에서 /token (grant_type=refresh_token) 호출 vs hidden iframe 로드 관찰 → automaticSilentRenew 의 실제 메커니즘 확정.

5. (별도 학습 단계) manual PKCE 구현 명세

Trace: PKCE-RFC7636-C2(verifier 생성·기록 + challenge 도출), PKCE-RFC7636-C3(code_challenge = BASE64URL-ENCODE(SHA256(ASCII(verifier)))), PKCE-RFC7636-C4(불일치 시 access 거부), verifier 저장 위치는 위임 raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff D6(full-page redirect 전제 → sessionStorage)

  • UNSUPPORTED_IMPL_DECISION: verifier sessionStorage 키명(pkce_verifier)은 임의 — trade-off: 고정키는 단순하나 multi-tab 동시 로그인 시 충돌(owner D6 는 state 포함 키를 대안 제시).
단계 명세 Trace
verifier 생성 crypto.getRandomValues(new Uint8Array(32)) → base64url → code_verifier (43128 char) PKCE-RFC7636-C2
challenge 도출 crypto.subtle.digest('SHA-256', TextEncoder().encode(verifier)) → base64url → code_challenge, code_challenge_method=S256 PKCE-RFC7636-C3
verifier 보관 sessionStorage.setItem('pkce_verifier', verifier) — 토큰 교환 성공 즉시 removeItem owner token-storage D6
token 교환 discovery metadata의 absolute token_endpoint로 POST. relative /token 금지 PKCE-RFC7636-C4 + static-only topology

엣지·실패·의존

R4(깊이 게이트) 캡처용. 정상 경로 외 실패/엣지/다른 계약 의존.

  • 실패·엣지 경로:
    • redirect_uri mismatch (localhost vs 127.0.0.1, 또는 /callback.html 오타): authorization server 가 exact-match 실패로 요청 거부(D5 / OA21-C5) → authorize 단계에서 에러. 등록값은 owner realm-client-export 소유.
    • CORS preflight 실패: nginx 80 → backend 8081 의 /api/me 호출 시 backend CORS 미설정이면 preflight(OPTIONS) 차단 → §구현가이드 4 OUT_OF_BRANCH_SCOPE(RS branch).
    • silent renew 실패: Keycloak cross-site + Safari ITP → hidden iframe 이 SSO cookie 못 읽음 → 어댑터가 full redirect fallback("silent" 상실). Chrome 일반 모드는 현재 동작(owner D3a)하나 정책 변동 리스크. → refresh_token grant 직접 사용으로 우회(owner token-storage D3).
    • in-memory 토큰 reload 소실: 페이지 새로고침 시 access/refresh token이 함께 소멸 → baseline은 재인증. silent SSO는 별도 조건부 비교다.
    • manual PKCE verifier 소실: full-page redirect 가 메모리 verifier 파괴 → sessionStorage 필수(owner D6). 콜백에서 verifier 부재 시 token 교환 실패(PKCE-RFC7636-C4: "Access is denied if they are not equal").
    • access token 만료 vs API 호출 race: /api/me 호출 순간 토큰 만료면 401 → silent renew 후 재시도 필요(구현 시 retry wrapper 고려, needs-confirmation).
  • 다른 계약 의존:

검증해야 할 주장

공식 문서 근거가 있어도 내 프로젝트에서의 동작을 자동으로 보장하지 않는다.

Claim Why uncertain How to verify Status
oidc-client-tsautomaticSilentRenew: true 가 iframe 기반이 아닌 refresh token grant 로 동작한다 OIDCTS-C4 와 C5 가 둘 다 지원 protocol 로 나열됨 — 어느 메커니즘이 default 인지 본 인용 범위 밖 dev 환경에서 token 만료 직전 DevTools Network 탭 캡처 → /token (grant_type=refresh_token) 호출 확인 vs iframe 로드 확인 planned
Keycloak SPA client 의 access token 만료가 5분이며 그 직전에 silent renew 트리거 본 branch 의 Sources 는 Keycloak 의 token lifetime default 를 다루지 않음 dev Keycloak realm settings > Tokens > Access Token Lifespan 확인 planned
crypto.subtle.digest('SHA-256', ...) + base64url 로 manual PKCE 구현이 oidc-client-ts 와 동일한 challenge 값 생성 RFC 7636 PKCE-RFC7636-C3 가 BASE64URL-ENCODE(SHA256(ASCII(verifier))) 공식 정의. 두 구현의 byte-level 일치는 실측 필요 동일 verifier 입력으로 manual 함수와 oidc-client-ts 내부 함수 결과 비교 planned
nginx 80 → backend 8081 CORS preflight 통과 (Authorization 헤더 허용 + credentials 정책) 본 branch 의 Sources 는 CORS 정책을 다루지 않음 (RS branch 소유) backend Spring Security CORS 설정 + DevTools Network preflight 응답 확인 planned
explicit in-memory userStore가 access/refresh token을 persistent storage에 남기지 않고 reload 뒤 재인증을 요구 active baseline은 정했지만 pinned version runtime 미검증 로그인 후 local/sessionStorage token 검색 → reload 뒤 getUser() null → 재인증 E2E planned
Keycloak client "PKCE method"=S256 토글이 code_challenge_method=plain 요청을 실제로 거부한다 KC-PKCE-C3 의 "applies... S256" 은 강제를 암시할 뿐 reject/error 를 명시 안 함(그 raw 의 Usage Boundaries) — server-side 강제는 realm-client-export owns dev Keycloak 에서 PKCE method=S256 설정 후 plain 요청 → redirect 에러 파라미터/HTTP status 확인 needs-confirmation
pinned oidc-client-ts의 default stateStore/userStore 종류와 차이 active baseline은 explicit store라 default에 의존하지 않지만 비교 설명의 사실 정확성은 미확인 pinned version docs와 runtime storage key를 각각 확인 needs-confirmation

마주친 문제

  • (구현 시작 후 추가) localhost vs 127.0.0.1 redirect_uri mismatch 예상.
  • (구현 시작 후 추가) CORS preflight 실패 예상 (backend CORS 설정 누락 시).
  • (구현 시작 후 추가) silent renew가 iframe 기반이면 third-party cookie 차단 이슈 — refresh token 기반인지 확인.

묶음

본 sub-sub-branch 는 leaf — 자식 자료 없음. Phase 3 P3A 실 구현 또는 외부 산출물 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.

오류 기록 (이 sub-sub-branch 작업 중 발생)

  • (없음 — 현재 documented-only 단계)

면접 준비 (이 작업에서 나올 수 있는 면접 질문)

  • (없음 — Phase 3 실 구현 단계에 누적)

관련 일일 노트

완료 후 정리

로컬 검증(login → /api/me → silent renew → logout 전체 흐름) 통과 시 plannedactually-implemented/locally-verified 승급.

  • PR 링크: (별도 keycloak-patterns repo)
  • 리뷰 메모:
  • 머지 결과 / 배포 환경: 로컬 docker-compose / 단일 EC2 시뮬레이션
  • wiki 추출 대상:
    • actually-implemented 항목: (구현 후 채움)
    • locally-verified 항목: (구현 후 채움)
    • prod-verified 항목: (없음)
  • 추출하지 않을 항목: 현재 전부 planned.