Skip to content
Hironobu Iga

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.

References