Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

포메

[flutter (dart)] Riverpod + GoRouter + FirebaseAuth 앱에 전역 Auth 검사 구조 만들기 본문

dart & flutter

[flutter (dart)] Riverpod + GoRouter + FirebaseAuth 앱에 전역 Auth 검사 구조 만들기

포포메 2025. 2. 9. 21:37

flutter 기본 개념을 배워가면서, 앱의 뼈대를 갖추면서 개발을 하고 있었기 때문에

일단 이곳저곳 급하게 구현한 곳이 많았다. 그리고 그것들을 바로 잡는 과정 중에 있었다.

 

기존 코드를 둘러보다가 문득 걱정이 들었다.

걱정의 대상이 된 코드는 다음과 같다.

 

기존 코드 (home_screen.dart)

 

HomeScreen은 goRouter 기반으로

final String memberHome = '/';

memberHome, 즉 root route에서 빌드하는 화면이었다.

해당 화면에는 FirebaseAuth.instance.authStateChages()기반의 StreamBuilder가 들어가 있었다.

그렇기 때문에 authState가 바뀔 때마다 자동으로 

NonMemberHome(), MemberHome() 위젯 중 적절한 것이 빌드되었다.

그랬기 때문에 root route에 접근할 경우 기능 상의 문제는 없었다.

문제 상황

 

계정 없는 유저가 root route가 아닌 곳으로 첫 접속을 한다면 ??

그럼 authState 검사를 우회할 수 있지 않을까.

 

물론, 쉽지 않다.

기본적으로 flutter 앱에서는 개발자가 의도하지 않은 라우팅이 어렵다.

챗지피티 답변

 

react 등으로 개발된 웹사이트였다면, 브라우저 주소창에 URL을 입력하여 사용자가 사실상 모든 route에 요청을 보내볼 수 있다.

그러나 flutter 앱에서는 정상적인 방법으로는 그럴 수 없다.

유저가 앱에 들어오면, 개발자가 의도한 route로 접근할 것이다.

그래서 보통은 root route에서만 로그인 검증을 해도 안전한 편이다.

 

그러나, 항상 여러 최악의 상황을 생각하는 것이  개발자의 자세 !!!!!!

 

(현재는 그럴 계획이 없지만), 이번 앱을 통해 flutter 웹 페이지까지 배포한다면 ??

 -> 유저가 브라우저 URL에 자유롭게 요청을 보낼 수 있을 것.

유저가 앱을 background로 돌려놓은 상황에, authState에 변화가 생긴다면 ? 그런데 그 유저는 root route가 아닌 다른 route에 있었다면?

 -> 유저가 다시 root route로 돌아가지 않는 한 로그인 검증을 우회할 수 있을 것.

 

위와 같은 잠재적 문제 때문에

유저가 어떤 상황에 있든, 최신의 정보로 로그인 검증을 할 필요가 있다고 생각했다.

 

 

해결법

 

일단 해결법의 대전제는 FirebaseAuth에서 Stream<User?>의 형태로 제공하는 authStateChanges()을 사용하는 것.

FirebaseAuth.instance.authStateChanges().listen을 하면, 즉 Stream을 구독하면

 

최신의 로그인 정보를 확실하게 받을 수 있다.

(사실 기존의 HomeScreen에서도 이거는 썼었다.)

 

문제는, 이 Stream을 어떤 곳에 넣어야 가장 내 앱의 코딩 컨벤션에 적합할지, 성능적으로 최선일지, redundancy가 적을지, 버그는 없을지 그런 것들이었다.

 

참고한 방법1 (채택하진 않음)

 

https://stackoverflow.com/questions/77249590/how-to-handle-authentication-in-flutter-with-go-router-and-firebase-authenticati

 

How to handle authentication in Flutter with go_router and Firebase Authentication when already on a route?

In my Flutter application, I'm using the go_router package to manage routes and Firebase Authentication for user authentication. I have several screens that require users to be authenticated to acc...

stackoverflow.com

 

authStateChanges에 따라 router.refresh()실행

--> 이후 goRouter의 redirect인자 하위의 다음과 같은 callback이 실행되어 로그인을 제어할 수 있다.

로그인이 되어있지 않을 경우, 강제로 LoginPage.route redirect하는 방식.

 

goRouter 소스코드를 까보면

routing 전에 항상 redirect인자의 함수를 실행하기 때문에 실현 가능성이 있는 방법이었다.

 

그러나 이 방법을 고르지 않았다.

문제 가능성과 redundancy가 예상되었다.

 

문제 가능성은, 이 방법으로는 유저가 로그아웃 버튼을 눌렀을 때 적절한 user정보를 제대로 반환받지 못한다.

 

FirebeseAuth.instance.currentUser는 

앱 최초 빌드 시의 유저 정보를 snapshot으로 가져온다.

이와 관련해 FirebaseAuth의 실제 소스코드에도 경고 주석이 달려있다.

 

그렇기 때문에 로그아웃 버튼을 눌렀을 때도 최신의 정보를 정확히 가져오려면 

redirect 콜백 내부에서 다시 한번 authStateChanges()를 조회하든가 해야한다.

 

근데 이건 redundancy다. 이미 router.refresh()를 trigger하는 부분에서 구독된 user정보를 활용하면 되는데 Stream을 한번 더 조회하는 것이다.

 

그래서 이 방법 폐기

 

 

참고한 방법 2 (채택하지 않음)

 

위에서 언급한 스택 오버플로우 사이트에도 제시된 방법이었고, 지피티도 추천한 방법이었다.

GoRouter의 refreshListenable 인자에 authStateChanges() Stream을 주입하는 방법.

 

결론부터 말하면 방법 1보다 더 좋지 않게 보였다.

 

또 등장한 GoRouter 소스 코드 ... ! ㄴㅇㄱ

refreshListenable은 Listenable type을 받는다. 즉

authStateChanges가 제공하는 Stream 객체를 그대로 전달할 수 없다.

이를 ValueNotifier, ChangeNotifier 이런 Listenable 객체로 감싸야 한다.

즉 굳이 쓰지 않아도 되는 Listenable을 하나 추가해야 한다.

 

심지어,

refreshListenable 이후에는 방법1과 동일하게 redirect callback으로 제어해야 한다.

오히려 방법 1에 비해서 redundancy를 한 스푼 더 얹는 방법이라고 생각했다.

 

 

결론은

Stream.listen -> refresh + redirect 콜백 폐기.

refreshListenable -> ChangeNotifier -> refresh + redirect 콜백도 폐기.

 

Auth를 2번 조회하게 하는 근본적인 문제는 refresh 콜백을 쓰는 것이라고 결론.

 

 

내 방법

riverpod on build, 

중앙 제어의 AuthNotifier에서 authStateChanges() Stream을 최초 1회만 구독.

해당 authState를, auth가 필요한 다른 모든 곳에서 사용

아래와 같이 최상단 MyApp에서 AuthNotifierProvider listen

굳이 FirebaseAuth를 직접 2회 이상 listen하지 않고 riverpod를 통해 1회만 listen하도록 최적화. 

++ 값이 변화될 경우, 굳이 refresh() + redirect callback을 쓰지 않고

goRouter.replace를 통해 원하는 경로로 바로 redirect 해주었다.

이미 nonMemberHome에 있거나, signInPath에 있는 경우 제외하고 다 nonMemberHome으로 집어넣어버리면 되겠지

 

테스트 결과 다 잘 되었다 !!!!

 

 

 

nextJS 앱은 그냥 middleware에다가 로그인 검사 로직을 넣으면 되니까 편했는데

flutter에서는 좀더 까다로웠다 ㅠ