Checking authentication state on navigation with Flutter's Navigation 2.0 and AutoRoute
Using auto_route's AutoRouteGuard to check the authentication state as navigation happens and send unauthenticated users to the sign-in screen. Covers the case where you inject with riverpod.
- Published
This article is also published elsewhere. https://iganin.hatenablog.com/entry/2021/08/24/215355
Originally written in Japanese. This is a translation of the same piece.
TL;DR
- Check the authentication state during navigation using the AutoRoute library’s
AutoRouteGuard- You can, for example, send the user to the sign-in screen when they are not authenticated
- It works even when you are injecting with riverpod
What this covers:
- How to check the authentication state during navigation with auto_route + riverpod
What it does not cover:
- Detailed usage of auto_route
- Detailed usage of riverpod
The idea
When building an app, there are times you want to check the authentication state during navigation and, if the user is not authenticated, either send them to the sign-in screen or present it.
That obviously applies to apps you cannot use without signing in, but it also covers cases where the normal screens work without authentication and everything from the purchase screen onwards requires it.
Below is how to do that with auto_route and riverpod.
Note: in a web app you have no idea which screen the user will arrive at, so this handling should be mandatory.
auto_route
A navigation library for Flutter. Written as below, it lets you navigate with router.push(XxxRoute()) and
router.pop(). Very convenient.
@AdaptiveAutoRouter(
replaceInRouteName: 'Page,Route',
routes: <AutoRoute>[
AutoRoute(path: '/', page: InitialPage, initial: true),
AutoRoute(path: '/sign_in', page: SignInPage),
AutoRoute(path: '/sign_up', page: SignUpPage),
],
)
class $Router {}
class App extends HookWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
~~~~~~
routeInformationParser: router.defaultRouteParser(),
routerDelegate: router.delegate(),
~~~~~~
}
Inserting an authentication check into navigation
Use AutoRouteGuard, which comes with auto_route.
class AuthGuard extends AutoRouteGuard {
@override
Future<void> onNavigation(
NavigationResolver resolver,
StackRouter router,
) async {
final isAuthenticated = await _isAuthenticated();
if (isAuthenticated) {
resolver.next(true);
} else {
// Depending on the case, re-authenticating here and deciding whether to navigate
// based on the result looks like a good option
router.replaceAll([SignInRoute()]);
resolver.next(false);
}
}
}
Once you have defined the AutoRouteGuard, pass the guard class in the guards argument of
AdaptiveAutoRouter’s AutoRoute, and the guard’s onNavigation will be called before navigating to that
screen.
@AdaptiveAutoRouter(
replaceInRouteName: 'Page,Route',
routes: <AutoRoute>[
AutoRoute(path: '/', page: InitialPage, initial: true, guards: [AuthGuard]),
AutoRoute(path: '/', page: SignInPage),
AutoRoute(path: '/', page: SignUpPage),
],
)
class $Router {}
That raises the question of how arguments get passed. It appears the class generated from
AdaptiveAutoRouter by build_runner automatically gets a constructor taking the guards in use as
arguments. So initialising the router above looks like this:
Router(authGuard: AuthGuard());
Bringing in riverpod
Since the guard is created and passed in when the router is initialised, DI with riverpod is easy too.
final authGuardProvider = Provider((ref) => AuthGuard(ref.read));
class AuthGuard extends AutoRouteGuard {
AuthGuard(this._read);
final Reader _read;
AuthRepositoryBase get _authRepository => _read(authRepositoryProvider);
~~~~
}
class App extends HookWidget {
App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final authGuard = useProvider(authGuardProvider);
final router = router(authGuard: authGuard);
~~~~
}
~~~~
}
Other notes
- Using a guard to check the authentication state is written up in auto_route’s README, so I take it to be the recommended approach.
- You can pass several with
guards: [], so it looks like you could also do things like sending the user back to a list screen when they navigate to a detail screen without the information it needs.