Files
document-haness/docs/keycloak/tech-log-studio/oauth-oidc-auth-boundary/concept/concept-browser-credential-storage.md
T

5.5 KiB

id, kind, slug, title, topic, project, status, version, basisVersion, studio
id kind slug title topic project status version basisVersion studio
bb5c37ae-2d94-48f7-ad4e-a37c61c3fd07 CONCEPT browser-credential-storage 브라우저가 credential을 보관하는 위치와 그 성질 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

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

브라우저에는 JavaScript memory, Session Storage, Local Storage, cookie가 있고 각각 수명과 접근 경로가 다르다. 어떤 credential이 어디에 있는지에 따라 새로고침 뒤 남는 것, JavaScript가 읽을 수 있는 것, 요청에 자동으로 붙는 것이 갈린다.

관계

  • OAuth Token과 Application Session을 구분하는 기준 여기 있는 값들에 각각 다른 이름을 쓰는 기준이다.
  • SPA에서 토큰을 직접 관리하면서 드러난 Browser Credential 경계 memory-only 구성을 실제로 확인한 기록이다.
  • BFF 인증 구조 설계 기준 브라우저에 session cookie만 두는 구조의 설계 항목이다.

본문

네 위치의 성질

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

자동으로 붙는다는 성질이 cookie를 credential로 쓸 때 CSRF 검증이 필요해지는 이유다.

userStore와 stateStore를 나눈다

oidc-client-ts의 UserManager는 두 저장소를 따로 받는다.

userStore  = InMemoryWebStorage
stateStore = sessionStorage

userStore는 로그인 뒤 User와 token set을 보관한다. stateStore는 redirect를 건너야 하는 authorization transaction을 보관한다.

두 저장소의 내용도 성격이 다르다.

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

state와 verifier는 Keycloak 왕복을 건너야 하므로 memory에 둘 수 없다. 이 값이 Session Storage에 있는 것과 token이 Web Storage에 있는 것은 다른 설정이다.

memory-only가 뜻하는 범위

InMemoryWebStorage는 로그인 결과를 브라우저의 영구 저장소가 아니라 실행 중 memory에만 둔다. 새로고침하면 User와 token이 초기화되고, Local Storage와 Session Storage에는 token 복사본이 남지 않는다.

memory-only는 persistent storage에 쓰지 않는다는 뜻이다. 실행 중 script가 응답이나 지역 변수를 읽을 수 없다는 뜻은 아니다. 브라우저 fetch를 hook하면 API 호출의 Bearer access token을 관측할 수 있다.

이 구성에서 관측한 두 결과는 다음과 같다.

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

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

HttpOnly는 JavaScript가 cookie 값을 직접 읽지 못하게 하는 속성이다. document.cookie로 조회되지 않지만 브라우저는 요청마다 붙여 보낸다.

AP2의 AP2_SESSION, AP3의 AP3_SESSION, AP4의 AP4_SESSION이 모두 HttpOnly다. 브라우저 JavaScript에 OAuth token을 전달하지 않는 구조에서도 이 cookie는 남는다. 브라우저에 없는 것은 애플리케이션이 쓰는 OAuth token이고, 인증 상태 자체는 이 cookie로 남아 있다.

Keycloak 도메인의 SSO cookie도 별도로 존재할 수 있다. 애플리케이션 memory의 User가 사라진 것과 IdP session이 끝난 것은 다른 사건이다.

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

AP2와 AP3의 session cookie는 server-side 상태를 찾는 열쇠다. 실제 access token과 refresh token은 authorized-client store에 있고 cookie 안에는 없다. cookie가 token map을 직렬화한다고 설명하면 구현이 틀리게 된다.

AP4에서 session-cookie-minimal=true를 쓰면 server-side session store 없이 edge가 필요한 최소 정보만 cookie 자체에 담는다. access·refresh·ID token은 여기에 들어가지 않는다. 그래서 AP4가 refresh token을 지속 보관한다고 말할 수 없다.

AP2와 AP3의 cookie는 server-side 상태를 찾는 열쇠이고, AP4의 cookie는 최소 상태를 담은 값이다. 두 cookie를 같은 문장으로 설명하지 않는다.

지금 구성은 cookie 속성과 redirect를 눈으로 확인하려고 HTTPS가 아닌 HTTP를 쓴다. 그래서 AP4_SESSIONSecurefalse다. 운영 HTTPS에서는 먼저 Secure=true를 설정해야 한다.

Secure, Domain, 만료를 로컬 YAML이 고정하지 않는 구성도 있다. 여기서 관측한 값을 운영 cookie 기본값으로 옮겨 적지 않는다.