Files
DongHyeonkaandClaude Fable 5.1 4d50bb939a docs(keycloak): adopt the decomposition contract, fix the redirect URI, strip evaluative prose
- 계약 채택 — 독자 질문, 후보 29건(PROMOTE 24 · MERGE_INTO 4 · KEEP_IN_SSOT 1). 게시 중
  17건은 전부 유지. 저장소 keycloak-pattern 은 패턴 넷이 브랜치로 갈라져 있어 revisions 로
  tip 넷을 적었다. keycloak-session-store 는 같은 저장소 @ cdac9b8
- 게시된 기록의 redirect_uri 가 SSOT·코드와 달랐다 — OAuth2callback.html → callback.html
  (frontend/src/app.js 에서 확인). 계약 title 이 기록과 다른 7건도 기록 쪽으로 맞췄다
- 미작성 1건 작성 — 패턴 검증을 실제로 돌릴 때의 안전한 순서(Reference)
- 리뷰 100건 반영 — 설명 뒤에 붙은 평가·차례 예고·독자 오해 가정·작성 지시를 지웠다.
  삭제가 남긴 조각 4건을 고치고, 원래부터 잘려 있던 로컬 미리보기 라벨 1건도 닫았다

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 12:39:20 +09:00

6.8 KiB

id, kind, slug, title, topic, topicName, project, status, version, basisVersion, studio, sourceRevision, source
id kind slug title topic topicName project status version basisVersion studio sourceRevision source
bb5c37ae-2d94-48f7-ad4e-a37c61c3fd07 CONCEPT browser-credential-storage 브라우저가 credential을 보관하는 위치와 그 성질 oauth-oidc-auth-boundary OAuth/OIDC 인증 경계 KeyCloak Patterns 게시 전 4 Keycloak 26.7.0 · oidc-client-ts · oauth2-proxy 7.15.2 https://hyeonworks.com/studio/documents/bb5c37ae-2d94-48f7-ad4e-a37c61c3fd07/edit keycloak-patterns-lab@2026-08
final/document.md#문제를-어렵게-만든-제약-브라우저에-없다

브라우저가 credential을 보관하는 위치와 그 성질

브라우저에는 JavaScript 메모리, Session Storage, Local Storage, 쿠키가 있고 각각 수명과 접근 경로가 다르다. 자격 증명(credential)을 어디에 두느냐에 따라 새로고침 뒤에 무엇이 유지되는지, JavaScript가 무엇을 읽을 수 있는지, 어떤 값이 요청에 자동으로 실리는지가 갈린다.

관계

  • OAuth Token과 Application Session을 구분하는 기준 여기 있는 값들을 어떤 이름으로 갈라 부르는지 정한 기록이다.
  • SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계 memory-only 구성을 실제로 확인한 기록이다.
  • BFF 인증 구조 설계 기준 브라우저에 세션 쿠키만 두는 구조의 설계 항목이다.

본문

네 위치의 성질

네 곳을 수명, JavaScript의 접근 여부, 요청 자동 첨부 세 가지로 갈라 보면 이렇다. 표의 HttpOnly는 JavaScript가 쿠키 값을 직접 읽지 못하게 하는 쿠키 속성인데, 나머지 성질은 뒤에서 따로 살펴보겠다.

위치 새로고침 뒤 JavaScript가 읽나 요청에 자동으로 붙나
JavaScript 메모리 초기화 읽는다 붙지 않는다
Session Storage 탭이 살아 있으면 유지 읽는다 붙지 않는다
Local Storage 유지 읽는다 붙지 않는다
HttpOnly 쿠키 만료까지 유지 읽지 못한다 붙는다

네 곳 가운데 쿠키만 요청에 자동으로 붙기 때문에, 쿠키를 자격 증명으로 쓰면 그 요청이 사용자가 의도한 것인지 확인하는 CSRF(Cross-Site Request Forgery, 사이트 간 요청 위조) 검증이 따라붙는다.

userStore와 stateStore를 나눈다

oidc-client-ts의 UserManager는 저장소를 두 개 따로 받는다. AP1은 이렇게 설정했다.

userStore  = InMemoryWebStorage
stateStore = sessionStorage

userStore는 로그인 뒤 User와 토큰 묶음을 보관하고, stateStore는 리다이렉트를 건너야 하는 authorization transaction을 보관한다.

저장소 들어가는 것 언제까지 필요한가
userStore User, access·refresh·ID token, 만료 시각, 프로필 로그인 상태가 유지되는 동안
stateStore state, PKCE verifier 콜백 처리가 끝날 때까지

state와 verifier는 Keycloak 왕복을 건너야 하므로 메모리에 둘 수 없다. 이 두 값이 Session Storage에 있는 것과 토큰이 Web Storage에 있는 것은 서로 다른 설정이다.

memory-only가 뜻하는 범위

InMemoryWebStorage는 로그인 결과를 브라우저의 영구 저장소가 아니라 실행 중 메모리에만 둔다. 그래서 새로고침하면 User와 토큰이 초기화되고, Local Storage와 Session Storage에도 토큰 복사본이 만들어지지 않는다.

이 설정은 저장 위치를 견준 뒤에 고른 것이다. Local Storage나 Session Storage에 토큰을 저장하면 새로고침은 편해지지만 노출 시간도 길어진다. HttpOnly 쿠키로 옮기는 것은 저장 위치만 바꾸면 끝나는 작업이 아니라 서버가 세션이나 토큰 중계를 맡는 AP3 계열 구조가 있어야 한다. refresh token만 mediator로 옮기는 AP2도 함께 검토했지만, 두 구조 모두 브라우저가 code를 교환하고 토큰 수명을 관리하는 모습을 가린다. AP1은 그 과정을 보여 주려고 access·refresh·ID token을 JavaScript 메모리에 두었고, 새로고침 뒤에 인증 상태를 복구하지 못하는 것을 감수했다.

memory-only는 영구 저장소에 쓰지 않는다는 뜻이지, 실행 중인 스크립트가 응답이나 지역 변수를 읽을 수 없다는 뜻은 아니다. 브라우저의 fetch를 가로채면 API 호출에 실린 Bearer access token을 그대로 관측할 수 있다.

Local Storage · Session Storage → access token 문자열 없음
실행 중 fetch hook            → Authorization: Bearer 관측됨

AP2에도 같은 구분이 필요하다. /token/access 응답의 access token은 JavaScript 지역 변수로 들어갔다가 다음 요청 헤더가 되고, 세 경계를 지나는 동안 영구 저장소에는 쓰이지 않는다.

HttpOnly 쿠키는 document.cookie로 조회되지 않지만, 브라우저는 요청마다 이 값을 자동으로 붙여 보낸다.

AP2의 AP2_SESSION, AP3의 AP3_SESSION, AP4의 AP4_SESSION이 모두 HttpOnly다. AP3와 AP4는 브라우저 JavaScript에 OAuth 토큰을 넘기지 않는다. 브라우저를 확인하면 HttpOnly 세션 쿠키가 남아 있고 요청할 때마다 자동으로 붙는다. 그래서 이 기록에서 「브라우저에 없다」는 말은 애플리케이션이 쓰는 OAuth 토큰에만 쓴다. 인증 상태 자체는 이 쿠키가 들고 있다.

Keycloak 도메인의 SSO 쿠키도 별도로 존재할 수 있다. 애플리케이션 메모리의 User가 사라진 것과 IdP(Identity Provider) 세션이 끝난 것은 다른 사건이다.

opaque는 내부 값을 브라우저가 해석하지 않고 그대로 돌려준다는 뜻이다.

AP2와 AP3의 세션 쿠키는 서버 쪽 상태를 찾는 열쇠다. 실제 access token과 refresh token은 authorized-client store에 있고 쿠키 안에는 없다.

AP4에서 session-cookie-minimal=true를 쓰면 서버 쪽 세션 저장소 없이 엣지가 필요로 하는 최소 정보만 쿠키 자체에 담는다. access·refresh·ID token은 여기에 들어가지 않는다. 그래서 AP4가 refresh token을 지속해서 보관한다고 말할 수 없다.

AP2와 AP3의 쿠키는 서버 쪽 상태를 찾는 열쇠이고, AP4의 쿠키는 최소 상태를 담은 값이다.

지금 구성은 쿠키 속성과 리다이렉트를 눈으로 확인하려고 HTTPS가 아니라 HTTP를 쓰고 있어서 AP4_SESSIONSecurefalse다. 운영 HTTPS에서는 먼저 Secure=true를 설정해야 한다.

Secure, Domain, 만료를 로컬 YAML이 고정하지 않는 구성도 있다.