Skip to content

Flow: AuthGate (인증 게이트)

비로그인 상태에서 보호된 액션을 호출하면 뜨는 로그인 유도 bottom sheet. App 전용 (Web 은 비로그인 피드라 게이트 개념 없음). lazy auth 모델의 핵심 — 진입 자체는 막지 않고, 보호 액션 시점에만 인증을 요구한다.

💡 시각 미리보기: design site / auth-gate

트리거 액션 (AuthGateAction)

게이트는 호출한 보호 액션에 따라 title/subtitle 카피가 달라진다. 4종:

action트리거 위치titlesubtitle
reveal공시 상세 — AI 분석 보기 (screen-disclosure-detail)"AI 분석을 보려면""AI 한입 분석 (주 10회 무료)"
watchlist대시보드 — 관심 종목 추가 (screen-dashboard)"관심 종목에 추가하려면""새 공시를 푸시로 알려드려요"
feedback공시 상세 — 분석 피드백"피드백을 남기려면""분석 품질 개선에 도움이 돼요"
signIn더보기 — 로그인 메뉴 (screen-settings)"공시한입에 로그인""관심 종목 알림 · AI 분석 · 피드백 등 더 많은 기능"

subtitle 의 quota 숫자("주 10회")는 WeeklyQuotaCopy.valueProp single source 를 통해서만 — 하드코딩 금지.

구성

bottom sheet (showAppBottomSheet, transparent chrome) 한 장:

영역내용
drag handle36×4 라운드 바 (상단 중앙)
titleaction 별 카피 (xl, w800)
닫기(X)우상단 PhosphorIcons.x IconButton — dismiss
subtitleaction 별 가치 카피 (sm, textSecondary)
에러 배너로그인 실패 시 errorBg 박스 (조건부)
소셜 버튼AppSocialButton kakao / google / apple (플랫폼별 순서)

소셜 버튼 순서 (플랫폼 분기)

정의 순서는 kakao → google → apple. 런타임에 플랫폼별로 재배열:

  • iOS — apple 을 맨 위로 이동 (apple, kakao, google). Apple 로그인 노출 (App Store 가이드라인)
  • Android — apple 제외 (kakao, google). Kakao 가 메인 마켓이라 최상단 유지

게이트 동작 (requireAuth)

  1. bootstrapped 가드 — 앱 cold start 직후 AuthController.refetch() 미완료 구간(저장 토큰 유효하지만 user == null)이면 로그인 사용자에게도 게이트가 잘못 노출됨. 첫 /auth/me 응답까지 최대 ~2초 폴링(50ms 간격) 후 판정. timeout 시 그냥 진행(네트워크 장애 무한 대기 방지)
  2. 인증됨onAuthenticated() 즉시 실행, 게이트 안 띄움
  3. 비로그인 → 게이트 표시 → 로그인 성공(true) 시 context.mounted + isLoggedIn 재확인 후 onAuthenticated() 실행. dismiss(false) 시 무동작

로그인 처리 (소셜 버튼 탭)

각 provider 동일 패턴 (_loginKakao / _loginGoogle / _loginApple):

  1. 해당 버튼 spinner + 나머지 버튼 disabled (_loading)
  2. {provider}LoginService.authenticate() → 토큰 → authController.handleAuthToken(token, provider:)
  3. handleAuthToken/auth/me 실패 시 throw 없이 logged-out 으로 반환 가능 → 결과 재확인: isLoggedIn 이면 게이트 닫고 성공 반환, 아니면 에러 배너 ("로그인 처리에 실패했어요. 잠시 후 다시 시도해주세요.")
  4. provider 별 취소 예외(*LoginCancelled)는 침묵, 실패 예외(*LoginFailed)는 에러 배너

Dismiss 정책

  • scrim(배경) 탭으로 안 닫힘 (isDismissible: false) — scrim 탭이 swipe 와 구분 안 돼 auth_gate_dismissed.reason 지표를 왜곡(panel 결정). dismiss 는 X 아이콘 + swipe down
  • 로그인 처리 중(_loading != null) dismiss 차단PopScope(canPop:) 가 swipe-to-dismiss + system back 가로챔. OAuth 콜백 후 handleAuthToken 진행 중 닫히면 "로그인은 됐는데 onAuthenticated 누락" race 발생
  • useRootNavigator: true_MainShell 의 floating glass nav bar 위로 덮기 위해

Analytics

이벤트시점payload
auth_gate_shown게이트 노출 (initState)action
auth_gate_dismissedX 클릭 → reason: close / swipe·back → reason: swipeaction, reason

dispose 시 종결 이벤트 미송출이면 swipe 로 보충 송출. 풀스크린 vs 모달 A/B 데이터 수집용.

상태 분기

  • A1. idle — 소셜 버튼 노출, 에러 없음 (기본)
  • A2. loading — 탭한 provider 버튼 spinner, 나머지 disabled, dismiss 차단
  • A3. error — 에러 배너 + 버튼 재활성 (재시도 가능)

Edge cases

  • cold start race — bootstrapped 가드가 흡수 (위 동작 1)
  • 로그인 성공 직후 logout 동시 발생onAuthenticated 직전 isLoggedIn 재확인으로 차단
  • handleAuthToken 부분 실패(/auth/me 단절) — 게이트 안 닫고 에러 배너 (버튼만 눌리고 무반응 방지)

관련 docs