Skip to content

fix: 앱(WebView) 로그인 풀림 — 원인 정리 및 수정 작업 #367

Description

@iOdiO89

배경

앱 환경에서 로그인이 간헐적으로 풀리는 현상. 웹은 미재현.
앱은 토큰 사본이 네이티브 SecureStore + WebView 쿠키 저장소 2곳이고, refresh(rotation)가 4개 경로(앱 부팅 / 웹 axios·SSE / Next 프록시 / iOS 공유 익스텐션)에서 독립적으로 일어나는데, 두 사본의 신선도를 비교하지 않아 죽은 토큰이 살아있는 토큰을 덮어쓰는 시나리오가 생긴다.

서버(piki-server) 실측

  • access 15m / refresh 14d / grace 10초 (동시 요청 버스트 흡수용, Redis Lua 원자 처리 — 구현 자체는 문제 없음)
  • grace 창 밖에서 옛 refresh 토큰 제시 = 재사용 의심 → family invalidation (유효하던 세션까지 서버에서 즉시 삭제)
  • refresh key가 refresh:{userId}유저당 1개 (디바이스 구분 없음)

→ 클라이언트가 오래된 토큰으로 refresh를 시도하는 행위 자체가 살아있는 세션을 죽이는 트리거.

발생 시나리오

  1. 콜드 스타트 로그아웃: 사용 중 프록시(proxy.ts)가 rotate → 네이티브는 모름(SecureStore stale) → 앱 종료 → 다음 실행 시 부팅 refresh가 stale 토큰 제시 → 서버 family invalidation → 완전 로그아웃 (이후 게스트 자동 로그인으로 덮여 "조용히 풀린" 것처럼 보임)
  2. 공유 익스텐션 역주입: 익스텐션이 rotate(SecureStore 최신) → 앱 열면 포그라운드 sync가 오래된 쿠키 토큰을 SecureStore에 무조건 덮어씀 → 다음 부팅에서 1번과 동일
  3. 마운트 레이스: index.tsx의 mount 시 syncCookies()와 부팅 refresh가 병렬 실행 — 순서에 따라 구 쿠키가 신규 토큰을 덮음
  4. 세션 쿠키 증발: 프록시 app 분기 Set-Cookie에 Max-Age가 없어 앱 종료 시 최신 토큰 쿠키가 사라짐 (1번을 악화)

클라이언트 수정 (이번 작업)

  • iat 비교 유틸 (apps/app/utils/jwt.ts) — JWT iat로 두 토큰 중 최신 판별, access 유효성 검사
  • TokenStorage.setTokensIfFresher — 저장된 refresh보다 최신일 때만 저장 (모든 역주입 차단)
  • 부팅 sync 개편 (useWebviewCookieSync) — ① refresh 전에 SecureStore vs 쿠키 중 최신 토큰 채택 ② access가 아직 유효하면 refresh 생략(불필요 rotation 제거) ③ 쿠키 시딩 시 토큰 exp를 expires로 부여(세션 쿠키 방지)
  • index.tsx — syncCookies·WEB_REQ_TOKEN_REFRESHED 핸들러에 iat 가드 적용, AppState background 전환 시에도 sync(종료 직전 유실 창 축소), mount 중복 sync 제거(레이스 해소)
  • proxy.ts — app 분기 Set-Cookie에 Max-Age 부여 (토큰 JWT의 exp에서 남은 시간을 계산해 사용 — 백엔드 TTL과 자동 일치)
  • useNativeLoginResult의 access 쿠키 만료(15m)와 refreshClientToken(1h) 불일치 — 동작엔 무해, 통일만 하면 됨

프록시 rotation의 네이티브 전파는 별도 웹 훅 없이 index.tsx의 AppState background 동기화가 담당 (같은 시점에 네이티브가 직접 쿠키를 읽는 쪽이 웹뷰 JS 생존 여부와 무관해 더 안정적).

Metadata

Metadata

Labels

APPGood for newcomersfixSomething isn't working

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions