diff --git a/frontend/lib/features/auth/presentation/login_screen.dart b/frontend/lib/features/auth/presentation/login_screen.dart index bd805a65..f9117728 100644 --- a/frontend/lib/features/auth/presentation/login_screen.dart +++ b/frontend/lib/features/auth/presentation/login_screen.dart @@ -77,6 +77,20 @@ class _LoginScreenState extends ConsumerState } } + // Helper to decode JWT and get User ID (sub claim) + String _getUserIdFromJwt(String jwt) { + try { + final parts = jwt.split('.'); + if (parts.length != 3) return 'unknown'; + final payload = utf8.decode(base64Url.decode(base64Url.normalize(parts[1]))); + final data = json.decode(payload) as Map; + return data['sub'] as String? ?? 'unknown'; + } catch (e) { + debugPrint("[JWT] Could not extract User ID (sub): $e"); + return 'unknown'; + } + } + void _handleTabSelection() { if (_tabController.index == 1 && _qrPendingRef == null) { _startQrFlow(); @@ -202,6 +216,7 @@ class _LoginScreenState extends ConsumerState 'unknown', [], 0, displayName, null, '', false, '', false, {}, '', '', '', false, 'enabled', [], [], [], ); final session = DescopeSession.fromJwt(jwt, jwt, dummyUser); + // Refresh Token을 LocalStorage에 저장 Descope.sessionManager.manageSession(session); // Notify and Go to Dashboard @@ -380,12 +395,55 @@ class _LoginScreenState extends ConsumerState } } + void _logTokenDetails(String jwt) { + try { + // JWT는 세 부분(Header, Payload, Signature)이 '.'으로 구분된 문자열입니다. 이를 분리합니다. + final parts = jwt.split('.'); + // 세 부분으로 정확히 나뉘지 않았다면 유효한 JWT가 아니므로 중단합니다. + if (parts.length != 3) return; + + // JWT의 두 번째 부분(Payload)은 Base64Url로 인코딩된 JSON 데이터입니다. + // 1. Base64Url 문자열을 디코딩하여 바이트 배열로 변환합니다. + // normalize()는 Base64 패딩(=) 문제를 처리해줍니다. + final decodedPayload = base64Url.decode(base64Url.normalize(parts[1])); + // 2. 바이트 배열을 UTF-8 형식의 일반 문자열(JSON)로 변환합니다. + final payloadJson = utf8.decode(decodedPayload); + // 3. JSON 문자열을 Dart에서 사용할 수 있는 Map 객체로 변환합니다. + final data = json.decode(payloadJson) as Map; + + // [FIX] 'exp'는 int 또는 double일 수 있으므로, 안전하게 num으로 처리합니다. + final accessExpValue = data['exp'] as num?; + // 'exp' (Expiration Time) 필드는 Access Token의 만료 시간을 나타냅니다. Unix 타임스탬프(초 단위) 값입니다. + // 이 값을 Dart의 DateTime 객체로 변환합니다. (1000을 곱해 밀리초 단위로 만듦) + final accessExp = accessExpValue != null + ? DateTime.fromMillisecondsSinceEpoch(accessExpValue.toInt() * 1000) + : 'N/A'; + // 'rexp' (Refresh Expiration) 필드는 Descope가 사용하는 커스텀 필드로, Refresh Token의 만료 시간을 ISO 8601 형식의 문자열로 나타냅니다. + final refreshExp = data['rexp'] ?? 'N/A'; + + // 확인된 만료 시간 정보들을 디버그 콘솔에 출력합니다. + debugPrint(""" + [Auth] Session Token Details --- + - Access Token Expires: $accessExp + - Refresh Token Expires: $refreshExp + """); + } catch (e) { + // JWT를 해석하는 과정에서 오류가 발생하면 콘솔에 에러를 출력합니다. + debugPrint("[Auth] Failed to decode or log token details: $e"); + } + } + void _onLoginSuccess(String token) { if (!mounted) return; + _logTokenDetails(token); + + // [FIX] 감사 로그에 실제 사용자 ID를 전송하기 위해 토큰에서 ID를 추출합니다. + final userId = _getUserIdFromJwt(token); + // Record Audit Log AuditService.logEvent( - userId: "unknown", // In real apps, parse token to get user ID + userId: userId, eventType: "LOGIN_SUCCESS", status: "SUCCESS", details: "User logged in via Baron SSO", diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index f235ba21..4c8ecfef 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -52,6 +52,7 @@ void main() async { // Load saved session if any try { + // 저장된 세션 불러옴 await Descope.sessionManager.loadSession(); } catch (e) { _log.warning("Failed to load session: $e"); @@ -115,9 +116,9 @@ final _router = GoRouter( ], redirect: (context, state) { final isLoggedIn = - Descope.sessionManager.session?.refreshToken.isExpired == false; + Descope.sessionManager.session?.refreshToken?.isExpired == false; final path = state.uri.path; - final isLoggingIn = path == '/' || path.startsWith('/verify/') || path.startsWith('/admin/') || path == '/approve'; + final isLoggingIn = path == '/' || path.startsWith('/verify/') || path == '/approve'; _routerLogger.fine("Redirect check - Path: $path, IsLoggedIn: $isLoggedIn");