Files
MyDoc/로그인과정.md
2026-06-16 10:25:29 +09:00

15 KiB

localhost:5000 로그인 화면 호출 흐름

이 문서는 브라우저에서 http://localhost:5000/로 접근했을 때 UserFront 로그인 화면이 뜨기까지의 순차 흐름을 정리합니다.

작성 기준:

  • 로컬 URL: http://localhost:5000/
  • gateway: baron_gateway
  • UserFront upstream: baron_userfront:5000
  • 로그인 화면 구현 파일: /mnt/e/h_workspace/baron-sso/userfront/lib/features/auth/presentation/login_screen.dart

1. 결론

로그인 화면의 대표 URL은 다음입니다.

  • 한국어: http://localhost:5000/ko/signin
  • 영어: http://localhost:5000/en/signin

로그인 화면의 실제 소스 파일 절대경로는 다음입니다.

/mnt/e/h_workspace/baron-sso/userfront/lib/features/auth/presentation/login_screen.dart

http://localhost:5000/로 접근하면 gateway가 UserFront 정적 앱을 내려주고, Flutter 앱이 기동된 뒤 GoRouter가 locale과 세션 상태를 판단하여 미로그인 사용자를 /{locale}/signin으로 보냅니다.

2. 전체 순차 흐름

2.1 브라우저 최초 요청

  1. 브라우저가 다음 URL을 요청합니다.
GET http://localhost:5000/
  1. 요청은 Docker host port 5000으로 들어오고 baron_gateway Nginx가 받습니다.

관련 파일:

/mnt/e/h_workspace/baron-sso/gateway/nginx.conf

관련 설정:

server {
    listen 5000;

    set $backend_upstream http://baron_backend:3000;
    set $userfront_upstream http://baron_userfront:5000;
    set $oathkeeper_upstream http://oathkeeper:4455;

    location / {
        proxy_pass $userfront_upstream;
    }
}
  1. /location /에 매칭되고 baron_userfront:5000으로 proxy됩니다.
browser -> localhost:5000 -> baron_gateway -> baron_userfront:5000

2.2 UserFront 정적 bootstrap 응답

  1. UserFront가 Flutter Web entry HTML을 반환합니다.

관련 파일:

/mnt/e/h_workspace/baron-sso/userfront/web/index.html
  1. index.html은 처음에 bootstrap shell을 보여줍니다. 이 시점에 보이는 것은 실제 Flutter 로그인 화면이 아니라 로딩용 HTML입니다.

주요 DOM:

<main id="baron-bootstrap-shell" aria-live="polite">
  <h1>Baron SW Portal</h1>
  <div class="loader" aria-hidden="true"></div>
  <p>Loading sign-in</p>
  <div class="signin-preview" aria-hidden="true">Sign in</div>
</main>
  1. index.html은 로컬 개발 환경에서 기존 service worker와 Flutter cache를 제거한 뒤 flutter_bootstrap.js를 로드합니다.

사용 함수/동작:

  • loadFlutter()
  • navigator.serviceWorker.getRegistrations()
  • registration.unregister()
  • caches.keys()
  • caches.delete()

이후 브라우저는 일반적으로 다음 정적 리소스를 가져옵니다.

GET /flutter_bootstrap.js
GET /manifest.json
GET /main.dart.mjs
GET /canvaskit/skwasm.js
GET /canvaskit/skwasm.wasm
GET /favicon.ico

2.3 Flutter 앱 시작

  1. Flutter runtime이 main.dartmain()을 실행합니다.

관련 파일:

/mnt/e/h_workspace/baron-sso/userfront/lib/main.dart

주요 함수:

  • main()
  • WidgetsFlutterBinding.ensureInitialized()
  • usePathUrlStrategy()
  • EasyLocalization.ensureInitialized()
  • LocaleRegistry.primeWithDefaults()
  • LoggerService.init()
  • runApp(...)
  1. runApp()은 다음 wrapper를 구성합니다.
EasyLocalization
  -> ProviderScope
    -> BaronSSOApp
  1. BaronSSOApp.build()MaterialApp.router를 생성하고 _router를 연결합니다.

주요 함수/객체:

  • BaronSSOApp
  • _BaronSSOAppState.build()
  • MaterialApp.router(...)
  • routerConfig: _router

3. GoRouter 라우팅 흐름

라우터는 /mnt/e/h_workspace/baron-sso/userfront/lib/main.dart에 정의되어 있습니다.

주요 객체:

final _router = GoRouter(
  initialLocation: '/',
  refreshListenable: AuthNotifier.instance,
  routes: [...],
  redirect: (context, state) { ... },
);

3.1 locale 결정

http://localhost:5000/에는 locale path segment가 없습니다. 따라서 앱은 선호 locale을 계산합니다.

사용 함수:

  • extractLocaleFromPath(Uri uri)
  • resolvePreferredLocaleCode()
  • normalizeLocaleCode(String? code)
  • buildLocalizedPath(String localeCode, Uri uri)
  • buildLocalizedHomePath(Uri uri, {String? preferredLocaleCode})

관련 파일:

/mnt/e/h_workspace/baron-sso/userfront/lib/core/i18n/locale_utils.dart

locale 결정 기준:

  1. 저장된 locale이 있으면 사용
  2. 없으면 브라우저/디바이스 locale 사용
  3. 지원하지 않는 값이면 fallback locale 사용

따라서 한국어 환경에서는 보통 ko, 영어 환경에서는 en이 됩니다.

3.2 세션 상태 확인

라우터와 앱 초기화 코드는 로컬 토큰 또는 쿠키 기반 세션 여부를 봅니다.

사용 함수:

  • _hasActiveLocalSession()
  • AuthTokenStore.getToken()
  • AuthTokenStore.usesCookie()
  • _shouldRunStartupSessionRecovery(Uri uri)
  • _silentSessionRecovery()
  • AuthProxyService.getMe()
  • AuthProxyService.getSessionStatus()

관련 파일:

/mnt/e/h_workspace/baron-sso/userfront/lib/main.dart
/mnt/e/h_workspace/baron-sso/userfront/lib/core/services/auth_token_store.dart
/mnt/e/h_workspace/baron-sso/userfront/lib/core/services/auth_proxy_service.dart

세션 확인 시 호출될 수 있는 backend API:

GET /api/v1/user/me

gateway 기준 흐름:

browser -> /api/v1/user/me -> baron_gateway location /api -> baron_backend:3000

3.3 미로그인 상태에서 최종 로그인 route로 이동

미로그인 상태라면 최종적으로 다음 경로로 이동합니다.

/{locale}/signin

예:

http://localhost:5000/ko/signin
http://localhost:5000/en/signin

관련 redirect 함수:

  • root route redirect: buildLocalizedHomePath(...)
  • global router redirect: redirect: (context, state) { ... }
  • private route guard: _redirectPrivateLocaleRoute(...)
  • locale entry fallback: LocaleEntryRedirectScreen._redirect()
  • signin path builder: buildSigninRedirectPath(...)

중요 분기:

  • locale이 없으면 locale이 붙은 path로 보정합니다.
  • public auth path는 그대로 통과합니다.
  • 로그인하지 않았고 private path 또는 locale root에 있으면 /{locale}/signin으로 보냅니다.
  • 로그인되어 있고 locale root에 있으면 /{locale}/dashboard로 보냅니다.

4. 로그인 화면 route와 화면 생성

로그인 화면 route는 main.dart/:locale/signin입니다.

브라우저 URL:

http://localhost:5000/ko/signin

라우터 path:

/:locale/signin

관련 코드 구조:

GoRoute(
  path: 'signin',
  builder: (context, state) {
    final loginChallenge = state.uri.queryParameters['login_challenge'];
    final redirectUrl =
        state.uri.queryParameters['redirect_uri'] ??
        state.uri.queryParameters['redirect_url'];
    return ScopedTheme(
      controller: ThemeController.auth,
      child: LoginScreen(
        key: state.pageKey,
        loginChallenge: loginChallenge,
        redirectUrl: redirectUrl,
      ),
    );
  },
)

생성되는 widget:

ScopedTheme
  -> LoginScreen

로그인 화면 구현 파일:

/mnt/e/h_workspace/baron-sso/userfront/lib/features/auth/presentation/login_screen.dart

주요 class/function:

  • LoginScreen
  • _LoginScreenState
  • _LoginScreenState.initState()
  • _LoginScreenState.build()
  • _handlePasswordLogin()
  • _handleLinkLogin()
  • _startEnchantedFlow()
  • _pollForSession()
  • _startQrFlow()
  • _startQrPolling()
  • _tryCookieSession()
  • _attemptOidcAutoAccept()
  • _acceptOidcLoginAndRedirect()
  • _onLoginSuccess()

5. LoginScreen 초기화 흐름

LoginScreen이 생성되면 _LoginScreenState.initState()가 실행됩니다.

초기화 순서:

  1. TabController(length: 3) 생성
  2. password/link/QR 입력 controller 준비
  3. drySend query parameter 확인
  4. redirect_uri 또는 redirect_url query parameter 확인
  5. login_challenge 확인
  6. magic link, short code, verification token이 있는지 확인
  7. QR 로그인 필요 안내 notice 확인
  8. OIDC login challenge가 있으면 자동 accept 시도
  9. tenant-info 조회로 login ID label 커스터마이징
  10. login challenge가 없으면 쿠키 세션 확인

초기 진입이 단순 http://localhost:5000/인 경우 보통 query parameter가 없으므로 다음 정도가 수행됩니다.

LoginScreen.initState()
  -> _resolveLoginChallenge()
  -> _attemptOidcAutoAccept()  // login_challenge 없으면 바로 return
  -> AuthProxyService.getTenantInfo()
  -> _tryCookieSession()

초기 화면 렌더링에 영향을 줄 수 있는 backend API:

GET /api/v1/auth/tenant-info
GET /api/v1/user/me

6. 로그인 화면 UI 구성

LoginScreen.build()는 탭 기반 로그인 UI를 구성합니다.

탭:

1. 비밀번호
2. 로그인 링크
3. QR 코드

관련 UI 코드:

  • TabBar
  • TabBarView
  • password tab
  • link tab
  • QR tab

6.1 비밀번호 탭

UI 요소:

  • 로그인 ID 입력
  • 비밀번호 입력
  • 로그인 버튼

주요 key:

password_login_id_input
password_login_password_input
password_login_submit_button

사용 함수:

  • _handlePasswordLogin()
  • AuthProxyService.loginWithPassword(...)
  • _onLoginSuccess(...)

호출 API:

POST /api/v1/auth/password/login

6.2 로그인 링크 탭

UI 요소:

  • 로그인 ID 입력
  • 로그인 링크 전송 버튼
  • short code 입력 UI
  • 재전송 버튼

사용 함수:

  • _handleLinkLogin()
  • _startEnchantedFlow(...)
  • _pollForSession(...)
  • _verifyShortCode(...)
  • AuthProxyService.initEnchantedLink(...)
  • AuthProxyService.pollEnchantedLink(...)
  • AuthProxyService.verifyLoginShortCode(...)

호출 API:

POST /api/v1/auth/enchanted-link/init
POST /api/v1/auth/enchanted-link/poll
POST /api/v1/auth/login/code/verify-short

6.3 QR 코드 탭

QR 탭은 선택될 때 QR flow를 시작합니다.

사용 함수:

  • _handleTabSelection()
  • _startQrFlow()
  • _startQrPolling()
  • _startCountdown()
  • AuthProxyService.initQrLogin()
  • AuthProxyService.pollQrStatus(...)

호출 API:

POST /api/v1/auth/qr/init
POST /api/v1/auth/qr/poll

7. OIDC login_challenge가 있는 경우

외부 RP 또는 Hydra 흐름에서 다음과 같은 URL로 들어올 수 있습니다.

http://localhost:5000/ko/signin?login_challenge=...

이 경우 LoginScreenlogin_challenge를 보관하고 다음을 시도합니다.

  1. _attemptOidcAutoAccept()
  2. 로컬 token 또는 cookie session이 있으면 backend에 login accept 요청
  3. backend가 반환한 redirectTo로 브라우저 이동

사용 함수:

  • _resolveLoginChallenge(...)
  • _attemptOidcAutoAccept()
  • _acceptOidcLoginAndRedirect(...)
  • _redirectToOidcTarget(...)
  • AuthProxyService.acceptOidcLogin(...)

호출 API:

POST /api/v1/auth/oidc/login/accept

로그인 화면에서 사용자가 비밀번호 로그인을 완료한 뒤에도 _onLoginSuccess()login_challenge를 감지하면 같은 accept 흐름을 수행합니다.

8. 페이지/route 목록

UserFront auth 관련 route는 locale 아래에 붙습니다.

브라우저 절대 URL 예시 GoRouter path 생성 화면 구현 파일
http://localhost:5000/ko/signin /:locale/signin LoginScreen /mnt/e/h_workspace/baron-sso/userfront/lib/features/auth/presentation/login_screen.dart
http://localhost:5000/ko/login /:locale/login LoginScreen /mnt/e/h_workspace/baron-sso/userfront/lib/features/auth/presentation/login_screen.dart
http://localhost:5000/ko/signup /:locale/signup SignupScreen /mnt/e/h_workspace/baron-sso/userfront/lib/features/auth/presentation/signup_screen.dart
http://localhost:5000/ko/registration /:locale/registration SignupScreen /mnt/e/h_workspace/baron-sso/userfront/lib/features/auth/presentation/signup_screen.dart
http://localhost:5000/ko/consent?consent_challenge=... /:locale/consent ConsentScreen /mnt/e/h_workspace/baron-sso/userfront/lib/features/auth/presentation/consent_screen.dart
http://localhost:5000/ko/forgot-password /:locale/forgot-password ForgotPasswordScreen /mnt/e/h_workspace/baron-sso/userfront/lib/features/auth/presentation/forgot_password_screen.dart
http://localhost:5000/ko/recovery /:locale/recovery ForgotPasswordScreen /mnt/e/h_workspace/baron-sso/userfront/lib/features/auth/presentation/forgot_password_screen.dart
http://localhost:5000/ko/reset-password /:locale/reset-password ResetPasswordScreen /mnt/e/h_workspace/baron-sso/userfront/lib/features/auth/presentation/reset_password_screen.dart
http://localhost:5000/ko/error /:locale/error ErrorScreen /mnt/e/h_workspace/baron-sso/userfront/lib/features/auth/presentation/error_screen.dart
http://localhost:5000/ko/dashboard /:locale/dashboard DashboardScreen /mnt/e/h_workspace/baron-sso/userfront/lib/features/dashboard/presentation/dashboard_screen.dart

9. 가장 중요한 파일 요약

목적 파일 절대경로
gateway proxy 설정 /mnt/e/h_workspace/baron-sso/gateway/nginx.conf
Flutter Web HTML bootstrap /mnt/e/h_workspace/baron-sso/userfront/web/index.html
앱 시작점과 GoRouter 정의 /mnt/e/h_workspace/baron-sso/userfront/lib/main.dart
로그인 화면 /mnt/e/h_workspace/baron-sso/userfront/lib/features/auth/presentation/login_screen.dart
로그인/API proxy client /mnt/e/h_workspace/baron-sso/userfront/lib/core/services/auth_proxy_service.dart
token/cookie session store adapter /mnt/e/h_workspace/baron-sso/userfront/lib/core/services/auth_token_store.dart
locale path helper /mnt/e/h_workspace/baron-sso/userfront/lib/core/i18n/locale_utils.dart
public auth path 판정 /mnt/e/h_workspace/baron-sso/userfront/lib/features/auth/domain/login_link_route_policy.dart

10. 짧은 호출 흐름 다이어그램

sequenceDiagram
    participant B as Browser
    participant G as baron_gateway<br/>localhost:5000
    participant UF as baron_userfront:5000
    participant FE as Flutter UserFront
    participant BE as baron_backend:3000

    B->>G: GET /
    G->>UF: proxy GET /
    UF-->>G: index.html
    G-->>B: index.html
    B->>G: GET /flutter_bootstrap.js
    G->>UF: proxy static asset
    B->>G: GET /main.dart.mjs 등
    G->>UF: proxy static assets
    FE->>FE: main()
    FE->>FE: MaterialApp.router(_router)
    FE->>FE: locale 결정
    FE->>FE: local token/cookie 상태 확인
    alt 미로그인
        FE->>FE: route -> /{locale}/signin
        FE->>BE: GET /api/v1/auth/tenant-info
        FE->>BE: GET /api/v1/user/me (cookie/session check 가능)
        FE->>B: LoginScreen 렌더링
    else 로그인됨
        FE->>FE: route -> /{locale}/dashboard
    end

11. 디버깅 포인트

로그인 화면이 뜨지 않을 때 먼저 확인할 것:

  1. baron_gateway가 떠 있는지
  2. baron_userfront가 healthy인지
  3. gateway 로그에 baron_userfront could not be resolved 또는 connect() failed가 있는지
  4. 브라우저 Network 탭에서 /flutter_bootstrap.js, /main.dart.mjs가 200인지
  5. Flutter route가 /ko/signin 또는 /en/signin으로 이동하는지
  6. /api/v1/auth/tenant-info, /api/v1/user/me 실패가 화면 렌더링을 막는지