Files
llm-wiki/raw/branch-notes/feature-keycloak-spa-token-storage-tradeoff.md
T

30 KiB
Raw 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-spa-token-storage-tradeoff (Token 저장 위치 trade-off — localStorage / sessionStorage / memory / httpOnly cookie) branch-note raw BR-KEYCLOAK-PATTERNS-OVERVIEW-006 project-work-item keycloak-patterns-overview WI-KEYCLOAK-PATTERNS-OVERVIEW-006
DEC-KEYCLOAK-PATTERNS-OVERVIEW-SPA-CLIENT-001@1
WI-KEYCLOAK-PATTERNS-OVERVIEW-003
1 feature-keycloak-spa-token-storage-tradeoff
keycloak-patterns
branch
keycloak-patterns
p2a
token-storage
xss
csrf
spa
owasp
2026-05-25 in-progress 92acc553e2b9cf25e7fb7a8d9030574d20891bef35e5e652038e480f067be1c3

branch: feature-keycloak-spa-token-storage-tradeoff — Token 저장 위치 trade-off

Layer: raw/branch-notes/raw/project-notes/keycloak-patterns-overviewWI-KEYCLOAK-PATTERNS-OVERVIEW-006 직접 branch. 목적: access_token / refresh_token을 SPA에서 어디에 저장할지 결정하기 위한 4 저장소(localStorage / sessionStorage / memory / httpOnly cookie)의 XSS·CSRF 노출 trade-off를 표로 정리. status_label: in-progress | review | merged | abandoned

부모 (필수)

raw/project-notes/keycloak-patterns-overview

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 1
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: 저장 위치별 browser token read/XSS surface가 재현되고 선택이 기록된다

상속한 프로젝트 결정

Decision Ref Project Summary Branch Application Source
DEC-KEYCLOAK-PATTERNS-OVERVIEW-SPA-CLIENT-001@1 AP1은 public client와 Authorization Code + PKCE를 사용한다 public SPA가 보유하는 token의 저장 위치와 XSS surface 비교에 적용한다 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

없음.

목표

OWASP HTML5 Storage cheat sheet: "Do not store sensitive data in Web Storage." — localStorage / sessionStorage는 동일 origin의 모든 JS가 접근 가능 → XSS 1회 발생 시 토큰 즉시 탈취. 반면 httpOnly cookie는 JS 접근 불가지만 CSRF surface가 생긴다. 두 surface 중 무엇을 선택해 무엇을 방어할지 의식적으로 결정해야 한다.

핵심 질문:

  • 4 저장소(localStorage / sessionStorage / memory / httpOnly cookie)의 XSS 노출과 CSRF 노출 비교?
  • refresh_token은 왜 access_token보다 더 엄격히 보호해야 하는가? (긴 TTL × 새 access_token 발급 권한)
  • OAuth 2.1 draft가 refresh_token 저장에 대해 권고하는 것은?
  • SPA reload 시 silent refresh / refresh_token cookie 패턴의 장단점?
  • Silent renew(iframe + prompt=none)는 왜 3rd-party cookie 제약으로 점점 어려워지는가?

본 sub-sub-branch는 저장소별 비교표 + 권장 조합 + reload UX 고려를 정리한다.

  • 이슈: (학습 노트, 이슈 없음)
  • PR: (구현 없음)

범위

포함 범위

  • 4 저장소(localStorage / sessionStorage / memory / httpOnly cookie)의 XSS·CSRF 노출 비교표 (문서화, documented-only)
  • pure SPA의 access_token / refresh_token memory-only baseline과 reload 재인증 결정
  • SPA reload 시 access_token 재획득 흐름(silent refresh / refresh_token grant / 재로그인) 옵션 비교
  • TMB/BFF variant를 별도 채택할 때 필요한 cookie/CSRF 계약의 경계 명시
  • PKCE code_verifier 저장 위치 결정 (D6)

제외 범위

의도적으로 제외한 것. 면접 등에서 "이건 범위에 없었습니다"라고 답할 근거.

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

TODO

각 항목 옆에 증거 등급 표기: actually-implemented | locally-verified | prod-verified | documented-only | planned | needs-confirmation

  • 4 저장소 노출 비교표 — 등급: documented-only
    저장소 JS 접근 XSS 노출 CSRF 노출 reload 후 유지 적합 토큰
    localStorage 높음 (모든 JS) 낮음 (자동 첨부 안 됨) 영구 권장 안 함
    sessionStorage 높음 (탭별, 모든 JS) 낮음 탭 내 (권장 안 함, 단 PKCE verifier는 가능)
    메모리 (JS 변수) 낮음 (런타임만, debugger 접근 가능하나 영속 X) 낮음 잃음 pure SPA access/refresh baseline
    httpOnly secure cookie 낮음 (JS 접근 불가) 높음 (자동 첨부) → SameSite + CSRF 방어 필요 cookie TTL TMB/BFF variant only
  • refresh_token 저장 권고 — 등급: documented-only
    • pure SPA baseline: 메모리 only. reload 시 재인증
    • TMB/BFF variant: httpOnly + Secure cookie. server-side endpoint와 CSRF 계약을 함께 소유할 때만
    • 절대 금지: localStorage / sessionStorage (RFC 6749 §10.4 refresh_token confidentiality)
  • access_token 저장 권고 — 등급: documented-only
    • 권장: 메모리 (JS 변수 / closure) — reload 시 silent refresh로 재취득
    • TTL: 5~15분 (짧을수록 탈취 시 피해 감소)
  • OAuth 2.1 draft 인용 — 등급: documented-only
    • "Refresh tokens MUST be sender-constrained or use refresh token rotation."
    • SPA 환경에서는 sender-constrained(mTLS / DPoP) 어렵 → rotation 의존
  • SPA reload 시 흐름 옵션 — 등급: documented-only
    • baseline: memory 소실 → 사용자 재인증
    • 대안: silent SSO는 별도 브라우저/배포 조건 검증
    • variant: httpOnly refresh cookie + /refresh는 TMB/BFF로 분류
  • Silent renew 함정 — 등급: documented-only
    • 1st-party context: Keycloak이 same-site면 동작
    • 3rd-party context: Safari ITP / Chrome 3rd-party cookie phase-out → Keycloak SSO cookie를 iframe에서 못 읽음 → silent renew 실패
    • 정정 (2026-07-18, → D3/D3a): "Chrome 3rd-party cookie phase-out" 은 부정확 — Google 2025-04-22 발표로 Chrome 일반 모드는 기본 미차단(Incognito 만 차단). cross-site 기본 차단이 확정된 것은 Safari(ITP) 뿐. Decision Evidence Map D3(Safari) + D3a(Chrome) 참조. raw/official-docs/chrome-third-party-cookie-policy-google-official
    • 대안: refresh_token grant 직접 사용 (cookie 또는 메모리)
  • XSS 발생 시 시나리오 — 등급: documented-only
    • localStorage: 즉시 토큰 탈취 + 영속 (브라우저 종료 후에도)
    • 메모리: 현재 페이지 세션 내 탈취 (이후 fetch 후킹은 가능하나 영속 X)
    • httpOnly cookie: JS 접근 불가지만 fetch(/api, {credentials: 'include'})로 공격자가 SPA 도메인 내에서 API 호출은 가능 → CSRF 토큰으로 추가 방어
  • CSRF 방어 (cookie 사용 시) — 등급: documented-only
    • SameSite=Strict (cross-site 자동 첨부 차단)
      • double-submit CSRF token (header X-CSRF-Token)
      • Origin / Referer 검증

진행 중 메모

작업하며 떠오른 메모. 자유 형식.

  • "메모리 저장은 안전하다"는 단순 명제는 아님 — XSS 페이로드가 fetch wrapper를 후킹하면 메모리에 있어도 모든 요청이 가로채짐. 단 영속성은 없음 (reload 시 사라짐).
  • BFF 패턴이 사실상 가장 깔끔한 해법이지만 백엔드 stateful + session 공유 필요 → P2A 본 branch에서는 SPA Direct를 채택했음.
  • PKCE code_verifier는 매우 단명(seconds) → sessionStorage도 허용 가능 (단 메모리가 더 안전).

결정 사항 (decisions)

추후 면접/회고에서 "왜 이렇게 했나" 답할 근거. 대안과 함께 기록.

  • 2026-05-25 (historical, superseded): P2A 권장 조합 = access_token 메모리 + refresh_token secure HttpOnly cookie.
  • 2026-07-18: pure SPA baseline = access/refresh token 모두 memory-only, reload 시 재인증. HttpOnly refresh cookie는 최소 TMB/BFF variant이며 AP1 baseline에 포함하지 않는다.
  • 2026-07-18: TMB/BFF variant를 채택할 때만 별도 cookie/CSRF owner를 지정한다. 현재 branch set에는 그 구현 owner가 없으므로 OWNER_REQUIRED로 남긴다.
  • 2026-05-25: localStorage 사용은 모든 토큰에 대해 금지로 기록 (OWASP).
  • 2026-05-25: silent renew는 3rd-party cookie 제약으로 long-term 권장 안 함 → refresh_token grant 직접 사용 우선.
  • 2026-07-18 (/branch-spec 자동조사 보강): D3 를 브라우저·토폴로지 조건부로 정밀화. (1) Keycloak same-site 면 silent renew 동작 / cross-site + Safari 는 ITP 로 구조적 실패(어댑터가 full-redirect fallback) → refresh_token grant 우선. (2) 정정 — "Chrome 3rd-party cookie phase-out" 전제는 부정확: Google 2025-04-22 발표로 Chrome 일반 모드는 기본 미차단(Incognito 만 차단) → 신규 D3a 로 분리. 근거: raw/official-docs/third-party-cookie-blocking-safari-webkit-official, raw/official-docs/silent-check-sso-third-party-cookies-keycloak-official, raw/official-docs/chrome-third-party-cookie-policy-google-official.
  • 2026-07-18 (/branch-spec 자동조사 보강): D6 를 UNSUPPORTED 에서 해소 — full-page redirect 전제에서 PKCE code_verifier 저장 = sessionStorage (in-memory 는 redirect 생존 불가, localStorage 는 OWASP 반대). 단 "BCP 직접 권고 아님(INFERENCE)" 명시 — Browser-Based Apps BCP 는 code_verifier 를 언급하지 않음. 근거: OWASP-HTML5-C4 + PKCE-RFC7636-C2.

결정-근거 매핑

각 결정의 직접 근거. 선택 조건 열(R2): 이 조건일 때 이 결정 / 다른 조건이면 어떤 대안. Strength 어휘: OWASP cheatsheet = official-reference, OAuth 2.1 / RFC 7636 = official-standard, WebKit / Chrome / Keycloak vendor doc = official-vendor-doc, Curity blog = company-case-study. company-tech-blog 단독으로 "공식 best practice" 단언 금지. D6 의 저장 위치 권고는 INFERENCE — BCP 직접 문장이 아니라 OWASP 원칙 + verifier lifetime + 실무 관행의 사슬(하단 Open Risk 참조).

Decision ID Decision 선택 조건 (언제 이 결정 / 언제 대안) Supporting Claims Evidence Strength Open Risk
D1 pure SPA baseline = access/refresh token 모두 memory-only, reload 시 재인증 server-side token custody가 없는 AP1이면 이 결정. 세션 지속이 필수면 D7의 TMB/BFF variant로 패턴을 바꾼다 raw/official-docs/owasp-html5-storage-xss-spa.md#OWASP-HTML5-C1, #OWASP-HTML5-C2, #OWASP-HTML5-C3 official-reference + project architecture decision memory token도 실행 중 XSS에 노출된다. 이 선택은 persistence를 제거할 뿐 XSS 자체를 제거하지 않음
D2 localStorage 사용은 모든 토큰에 대해 금지 N/A (무조건) — XSS 위협 모델을 가정하는 모든 SPA. XSS 를 위협 모델에서 완전 배제 가능하면 예외 후보이나 OWASP 는 그 가정 불허 raw/official-docs/owasp-html5-storage-xss-spa.md#OWASP-HTML5-C1, raw/official-docs/owasp-html5-storage-xss-spa.md#OWASP-HTML5-C2, raw/official-docs/owasp-html5-storage-xss-spa.md#OWASP-HTML5-C3 official-reference sessionStorage 도 동일 위협 (OWASP-HTML5-C4 Does not prove: "sessionStorage 가 XSS 에 안전하다는 뜻은 아님 — C2/C3 는 these objects 즉 둘 다에 적용"). 본 branch 본문 표의 "sessionStorage XSS 노출 높음" 은 정합
D3 Keycloak hidden-iframe silent renew(prompt=none)는 Keycloak cross-site + Safari 에서 구조적으로 실패 → refresh_token grant 직접 사용(rotation 의존) 우선 Keycloak same-site(SPA 와 동일 registrable domain) → silent renew 동작(유지 가능). Keycloak cross-site + Safari(ITP) → 실패(어댑터가 full-redirect fallback → "silent" 상실) → refresh_token grant. Chrome cross-site 는 D3a 참조 raw/official-docs/silent-check-sso-third-party-cookies-keycloak-official.md#KC-JSADAPTER-C1, ...#KC-JSADAPTER-C2, ...#KC-JSADAPTER-C3, ...#KC-JSADAPTER-C4, ...#KC-JSADAPTER-C5, raw/official-docs/third-party-cookie-blocking-safari-webkit-official.md#WEBKIT-3PC-C1, ...#WEBKIT-3PC-C3 official-vendor-doc (Keycloak + WebKit) same-site vs cross-site 판정 단위(eTLD+1)의 직접 인용은 본 Sources 에 미확보(WebKit 블로그에 없음 → webkit.org/tracking-prevention 별도 아카이빙 필요, Should-fix). "silent renew 는 항상 안 된다" 는 과장 — same-site 배포면 동작. refresh_token grant 의 저장 위치 문제는 D1 · §엣지 참조
D3a Chrome 은 (2026-07-18 조사 시점 stated policy) 일반 모드에서 3rd-party cookie 기본 미차단(Incognito 만 차단) → "Chrome 3rd-party cookie phase-out" 통념은 부정확 Chrome 일반 모드 + cross-site → silent renew 현재 동작(단 정책 불안정). Chrome Incognito → 차단 → 실패. 사용자가 수동 3PC off → 브라우저 무관 실패 raw/official-docs/chrome-third-party-cookie-policy-google-official.md#CHROME-3PC-C1, ...#CHROME-3PC-C3 official-vendor-doc Google 정책은 2020~2025 수차례 번복(2025-04-22 철회) → "확정적 장기 사실" 인용 금지, "조사 시점 stated policy" 로만. 장기 아키텍처를 현재 Chrome 정책에 고정하는 것 비권장. (skycloak.io 등 "Chrome deprecation 중" 주장은 이 공식 vendor 소스와 상충 → 채택 안 함)
D4 refresh_token 은 rotation 에 의존 (sender-constrained mTLS/DPoP 어려움) SPA(public client)라 sender-constrained(mTLS/DPoP) 어려움 → rotation. mTLS/DPoP 지원 환경(confidential client 전환 등)이면 sender-constrained 상위. rotation 메커니즘·재사용탐지는 sibling raw/branch-notes/feature-keycloak-refresh-token-rotation 위임 raw/official-docs/oauth-v2-1-draft-ietf.md#OA21-C3 (refresh token 은 scope + resource server 에 bound MUST), raw/official-docs/oauth-v2-1-draft-ietf.md#OA21-C4 (BFF 권고) official-standard 본문 "Refresh tokens MUST be sender-constrained or use refresh token rotation" 의 직접 verbatim 은 본 branch Sources 의 OAuth 2.1 발췌(OA21-C1~C6)에 미포함 — sibling refresh-token-rotation branch 가 rotation 상세를 owns. 현 D4 는 OA21-C3(bound) + OA21-C4(BFF)로 부분 corroborate
D5 refresh token 탈취 시 유효 기간 동안 victim 데이터 접근 가능 — SPA Direct 의 핵심 위험 N/A (위험 진술). 이 위험 감수 불가 → BFF 전환(refresh_token 을 브라우저에서 제거, D1 대안) raw/company-tech-blogs/curity-bff-pattern-spa.md#CURITY-BFF-C6, raw/company-tech-blogs/curity-bff-pattern-spa.md#CURITY-BFF-C2 company-case-study Curity vendor 권고. 공식 표준 측 corroborate 는 OA21-C3(refresh token binding MUST)와 결합. rotation mitigation 효과는 본 인용 미포함(sibling 위임)
D6 PKCE code_verifier 저장 = sessionStorage (full-page redirect 전제) — in-memory 는 redirect 생존 불가, localStorage 는 OWASP 의 "persistence 불필요 시 sessionStorage" 조건에 반함 full-page redirect flow(탭 전체 navigate) → 메모리 verifier 파괴 → sessionStorage. popup/iframe 로 부모 탭 메모리 유지 가능하면 in-memory 가 더 안전. multi-tab 로그인 UX 요구 → cookie transaction(Auth0 useCookiesForTransaction) 별도 검토(N=3 범위 밖) raw/official-docs/owasp-html5-storage-xss-spa.md#OWASP-HTML5-C4 (persistence 불필요 시 sessionStorage), raw/official-docs/oauth2-pkce-rfc-7636.md#PKCE-RFC7636-C2 (verifier = 생성·기록 per-transaction secret) official-reference(OWASP) + official-standard(RFC 7636) + INFERENCE(저장 위치) "sessionStorage 가 BCP 권고" 표현 금지 — OAuth 2.0 for Browser-Based Apps BCP 는 code_verifier 를 0회 언급(§8 은 token 전용, oauth2-browser-based-apps-ietf-draft 확인). 저장 위치 결정은 OWASP 일반 원칙 + verifier lifetime + 실무 관행의 inference 사슬이지 단일 official 직접 인용 아님. verifier(sessionStorage) 탈취는 authorization code 없이 무가치 → token 탈취보다 심각도 낮음
D7 HttpOnly refresh cookie는 TMB/BFF variant에서만 허용 reload 없는 세션 지속이 memory-only UX보다 중요하고, server-side /refresh·cookie lifecycle·CSRF negative test owner를 함께 둘 때만. 그 계약이 없으면 D1 유지 raw/company-tech-blogs/curity-bff-pattern-spa.md#CURITY-BFF-C1, #CURITY-BFF-C6 company-case-study + architecture boundary 현재 구현 owner 없음(OWNER_REQUIRED). audience-validator는 bearer 검증 owner이지 cookie/CSRF owner가 아님

구현 가이드

본 branch 는 documented-only — 여기서의 "구현" 은 다운스트림 구현 branch(raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce)가 소비할 저장 위치 배치 명세다. 각 row 는 Decision ID + Supporting Claim ID 를 Trace 하거나 UNSUPPORTED_IMPL_DECISION 라벨을 단다(CLAUDE.md §15.5 3-rule).

1. 토큰·secret 저장 위치 배치 명세

Trace: D1 (OWASP-HTML5-C1/C2/C3, CURITY-BFF-C6), D2 (OWASP-HTML5-C1~C3), D6 (OWASP-HTML5-C4, PKCE-RFC7636-C2)

  • UNSUPPORTED_IMPL_DECISION: (1) refresh_token cookie 의 Path 범위, (2) verifier sessionStorage 키명 — 아래 표에 개별 명시.
대상 저장 위치 속성 / 키 Trace 라벨
access_token 메모리 (모듈 스코프 closure 변수, non-exported) reload 시 §2 흐름으로 재취득 D1 / OWASP-HTML5-C1·C2
refresh_token 메모리 (access_token과 동일한 in-memory store) reload 시 폐기하고 재인증 D1 / OWASP-HTML5-C1·C2
localStorage / sessionStorage 토큰 저장 금지 D2 / OWASP-HTML5-C1·C2·C3
PKCE code_verifier sessionStorage 토큰 교환 성공 즉시 removeItem D6 / OWASP-HTML5-C4, PKCE-RFC7636-C2 UNSUPPORTED_IMPL_DECISION: 키명(예 kc_pkce_verifier)은 임의 — trade-off: 키에 state 포함(...-${state})하면 multi-tab 동시 로그인 충돌 방지(Auth0 관행), 고정키는 단순하나 탭 충돌

HttpOnly refresh cookie는 D7 variant다. backend /refreshSet-Cookie하고 CSRF를 검증해야 하므로 pure SPA 배치표에 섞지 않는다.

2. reload 후 access_token 재취득 흐름

Trace: D1, D3 (Safari cross-site: silent renew 실패), D3a (Chrome normal-mode: 현재 미차단이나 정책 불안정)

옵션 흐름 언제 이 옵션 Trace
(a) 같은 page session의 refresh_token grant memory refresh_token으로 새 access_token을 받아 둘 다 memory에 갱신 reload 전 활성 session에서 rotation을 시연할 때 D1/D4, sibling raw/branch-notes/feature-keycloak-refresh-token-rotation
(b) silent renew (hidden iframe + prompt=none) iframe 에서 Keycloak SSO cookie 로 재발급 Keycloak same-site(eTLD+1 동일)일 때 안정. Chrome normal cross-site 도 현재 동작하나 정책 불안정 D3, D3a
(c) 메모리 only + 재인증 reload 시 토큰 소실 → 사용자 재인증 pure SPA baseline D1

3. TMB/BFF variant의 cookie·CSRF 선행 계약

Trace: D1 (OWASP-HTML5-C5: cookie 는 path 제한 가능하나 CSRF 는 별도 surface)

  • OUT_OF_BRANCH_SCOPE / OWNER_REQUIRED: pure SPA에는 이 계약을 적용하지 않는다. D7 variant를 채택할 때 /refresh, Set-Cookie, logout/revoke, CSRF token 발급·검증과 negative test를 한 별도 owner D-row에 먼저 배정한다. raw/branch-notes/feature-keycloak-spring-rs-audience-validator는 bearer JWT 검증 owner이므로 목적지가 아니다.
  • UNSUPPORTED_IMPL_DECISION: double-submit vs synchronizer token 선택은 본 Sources 직접 근거 없음 — Spring 기본은 synchronizer. trade-off: double-submit 은 stateless(세션 불요)하나 XSS 에 상대적으로 약함.
  • 저장측 요구: SameSite=Strict + double-submit CSRF token(요청 header) + Origin/Referer 검증.

엣지·실패·의존

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

  • 실패·엣지 경로:
    • XSS 발생 시:
      • localStorage/sessionStorage 토큰: 즉시 전량 탈취(+ localStorage 는 영속) — D2 (OWASP-HTML5-C2/C3)
      • 메모리 access_token: 런타임 XSS 가 fetch wrapper 후킹 시 세션 내 탈취 가능, 단 영속 X(reload 소멸)
      • D7 variant의 HttpOnly cookie: JS가 raw token을 읽지 못해도 XSS가 활성 session으로 요청을 대행할 수 있고 browser 자동 첨부로 CSRF surface가 생김 → 별도 owner 계약 필요
      • PKCE verifier(sessionStorage): 탈취돼도 authorization code 없이는 무가치 → token 탈취보다 심각도 낮음(D6 INFERENCE 근거)
    • reload: 메모리 access_token 소실 → §구현가이드 2 재취득 필수. 재취득 실패 시 재로그인.
    • silent renew 실패: Keycloak cross-site + Safari ITP → hidden iframe 이 SSO cookie 못 읽음 → Keycloak 어댑터가 full redirect 로 fallback("silent" 상실). Chrome normal-mode 는 현재 동작(D3a)하나 정책 변동 리스크.
    • refresh_token 탈취: 유효기간 내 victim 데이터 접근(D5, CURITY-BFF-C6) → rotation 재사용 탐지(sibling 위임).
    • PKCE verifier 소실: full-page redirect 가 메모리 verifier 파괴 → sessionStorage 필수(D6). 콜백에서 verifier 부재 시 token 교환 실패(PKCE-RFC7636-C4: "Access is denied if they are not equal").
    • 동시성: multi-tab 동시 로그인 → sessionStorage 탭 격리로 verifier 충돌 방지(D6 채택 이유); refresh_token grant rotation 시 동시 refresh race(두 번째 요청이 무효화 토큰 사용) — rotation 구현 detail 은 sibling 위임.
  • 다른 계약 의존:

검증해야 할 주장

Claim Why uncertain How to verify Status
pure SPA에서 access/refresh token을 memory-only로 두고 reload 시 재인증하는 baseline 문서 근거는 있으나 실 SPA 구현 없음 raw/branch-notes/feature-keycloak-vanilla-js-spa-pkce에서 로그인→API→reload→user/token 소실→재인증을 E2E 확인 planned
D7 HttpOnly refresh-cookie variant의 server endpoint·CSRF 계약 현재 owner와 구현 artifact가 없음 variant owner branch와 D-row를 먼저 만든 뒤 /refresh Set-Cookie, CSRF negative test, logout/revoke E2E 확인 blocked-on-owner
Safari cross-site silent renew 실패와 Chrome 조사시점 정책의 runtime 동작 D3/D3a의 vendor 근거는 확보됐지만 본 topology E2E 미실행 same-site/cross-site를 나눠 Safari와 Chrome에서 hidden iframe/full redirect를 관측 planned (source-resolved, runtime-unverified)
D7 variant의 CSRF 방어 조합 pure SPA 범위 밖이고 owner 미정 owner 지정 후 위협 모델에 맞는 SameSite/CSRF token/Origin 검증과 negative E2E를 명세 blocked-on-owner
PKCE code_verifier sessionStorage 선택 RFC·OWASP 근거 사슬은 확보됐지만 직접 BCP 권고가 아닌 inference full-page redirect 전후 verifier 생존과 callback 직후 제거를 E2E 확인 planned (inference-grounded, runtime-unverified)
본 branch 4 저장소 비교표의 각 셀이 OWASP 또는 OAuth 2.1 draft 의 정확한 quote 로 직접 뒷받침되는지 본문 표는 종합 판단 — 셀별 source mapping 부재 각 셀마다 supporting claim 표기 또는 본 branch 본문 통찰임을 명시 planned

마주친 문제

  • 이슈 1: 메모리 저장은 reload 시 토큰을 잃음 → UX 저하 vs 보안 trade-off.
    • 원인: SPA가 매 reload마다 새로 부트스트랩되므로 closure 변수는 사라짐
    • 시도: (구현 없음)
    • 해결: pure SPA baseline은 reload 시 재인증. 무중단 UX가 필수면 D7 TMB/BFF variant를 별도 채택 — documented-only

묶음

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

근거 자료

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

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

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

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

관련 일일 노트

완료 후 정리

머지/종료 시점에 채움. /ingest가 이 섹션을 기준으로 wiki/projects/에 추출.

  • PR 링크: (미구현 — 문서까지만)
  • 리뷰 메모:
  • 머지 결과 / 배포 환경: 없음 (P2A는 documented-only 범위)
  • wiki 추출 대상: 현 단계 없음. 추후 wiki/concepts/spa-token-storage-trade-off.md로 합성 후보.
  • 추출하지 않을 항목: P2A 구현 없음. documented-only 유지.