avatarsimbu

Summary

The provided content discusses the implementation of authentication features within a Flutter application, including the use of Microsoft AD or Google Firebase, and the integration of these features into the application's lifecycle and navigation events.

Abstract

The article delves into the process of incorporating modern authentication methods into a Flutter application, specifically focusing on the DigestablePrologue and DigestableMe projects. It outlines the high-level business requirements for secure access to screens and APIs, and details the collaborative process of defining specifications through workshops. The author describes the creation of executable specifications and the partial implementation of authentication services, with plans to add AD & Firebase authentication in the future. The article also covers the use of an event bus for navigation events and application state monitoring, the challenges faced during feature testing, and the strategies employed to overcome these challenges, including the use of separate test suites and event-driven architecture to simulate API requests and restricted screen access. The author emphasizes the importance of feature tests in maintaining application integrity and plans to automate the implementation of feature steps.

Opinions

  • The author values the abstraction and reusability of authentication services to reduce maintenance and streamline updates.
  • There is an emphasis on the importance of feature tests to ensure that changes to the application do not introduce regression bugs.
  • The author acknowledges the complexity of feature testing for certain aspects of authentication and proposes practical solutions to work around these difficulties.
  • The use of an event bus is seen as a beneficial approach to decouple navigation and API request logic, facilitating easier testing and potential extension for application monitoring.
  • The author suggests a disciplined approach to implementing feature steps and expresses an intention to automate this process to improve efficiency.
  • There is a recognition of the potential for future expansion in terms of role-based route authorization, which is currently set to allow access to everyone but can be restricted as needed.
  • The article concludes with a nod to community resources and further reading on naming conventions, event-driven architecture, and Flutter-specific documentation, indicating a commitment to best practices and continuous learning.
Join Medium to view all my articles.

Flutter — Authentication

Authentication (from Greek: αὐθεντικός authentikos, “real, genuine”, from αὐθέντης authentes, “author”)

Wiki

If I trust I know who you are, then I can give you access to your personal application data.

Authentication is probably the biggest cross cutting concern that applications have to deal with.

Adding it as a feature to DigestablePrologue allows us to abstract and reuse it many times, and reduce maintenance, by having a single set of code to update.

Lets start with a high level, intentionally vague business requirement:

“The application will be able to secure access to screens and the API’s, using modern authentication”

Julia from Proposition

After an initial conversation we settle on the some high level acceptance for the requirement:

  • Slide Up Login Screen.
  • Authenticate with Microsoft AD or Google Firebase.
  • Logout in settings.

Then we held a workshop to create the specification’s by example:

Login slide up specification
Auth service specification
Logout setting specification

And converted them into executable specifications:

Login screen feature tests
Authentication service feature tests
Logout setting feature tests

To reduce the length of this post, I’ve partially implemented the logout settings skipped implementing AD & Firebase authentication services which will be added soon.

As a result the feature tests results no longer fully reflect the initial specifications, but change of scope or direction is normal once you get into the devil in the details.

All the articles I’ve published on the subject of auth and data access will be tied together soon by a summary article, a mini series.

Ta Da

The environment is read from the app_config.json file that is loaded when the application starts and is injected by the CodeMagic integration build for live and UAT:

The authentication service gets selected based on the environment:

AuthenticationServiceStateNotifier selectAuthenticationServiceByEnvironment() {
  var environment = GlobalEnvironmentValues.instance.environment;

  AuthenticationService authenticationService = environment == Environments.live
      ? LiveAuthenticationService()
      : environment == Environments.uat
          ? UatAuthenticationService()
          : StubbedAuthenticationService();

  return AuthenticationServiceStateNotifier(authenticationService);
}

final authenticationServiceProvider = StateNotifierProvider<
    AuthenticationServiceStateNotifier, AuthenticationService>(
  (ref) => selectAuthenticationServiceByEnvironment(),
);

When the application starts it detects the stubbed authentication service and automatically authenticates the user:

if (ref.read(authenticationServiceProvider).typeName ==
        AuthenticationService.authenticationServiceTypeNameStubbed) {
        ref.read(authenticationProvider.notifier).setIsAuthenticated(true);
}

The settings screen was added using a great package settings_ui, it allows the user to sign out, and calls the signOut method on the authentication service:

Sign Out setting

All covered by our automated feature tests:

Login feature test report
Authentication feature test report

In the future post when we add the UAT and Live environments, it will route to the login screen when not authenticated and call the real authentication service to get the access tokens that will be used by Flutter Data to make secure API requests.

The login screen and the authentication services have been added to DigestablePrologue and settings screen to DigestableMe.

XP

Raise navigation events

I order to respond to navigation, I’ve add an observer to GoRouter

MaterialApp.router(
	...
    navaigationObservers: [
       NavigationEventsObserver(ref.read(eventStoreProvider))
    ],
	...
)

It raises a Navigated event on the event bus each time navigation occurs:

/// Raises Nativigated events when the GoRouter navigates.
class NavigationEventsObserver extends NavigatorObserver {
  final EventStore eventStore;
  NavigationEventsObserver(this.eventStore);
  

  @override
  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
    raiseNavigatedEvent(route.settings.name ?? "");
  }

  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    raiseNavigatedEvent(route.settings.name ?? "");
  }

  @override
  void didReplace({ Route<dynamic>? newRoute, Route<dynamic>? oldRoute }) {
    raiseNavigatedEvent(newRoute?.settings.name ?? "");
  }
  
  void raiseNavigatedEvent(String routeName) {
    even(fn)tStore.bus.fire(Navigated(routeName));
  }
}

The tests can then use this to prove a navigation action has taken place, currently the event store just keeps the last Navigated event.

It is likely that I will extend this to add application monitoring by listening to events and logging them.

Flutter App Lifecycle

To enforce the required authentication on wake I needed to capture the application state(AppLifecycleState) with an override.

@override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    ref.read(appLifecycleStateProvider.notifier)
		.setLifecycleState(state);
    ref.read(authenticationServiceProvider.notifier)
		.checkAuthenticated();
  }

The observable lifecycle events (AppLifecycleState):

  • inactive — The application is in an inactive state and is not receiving user input. This event only works on iOS, as there is no equivalent event to map to on Android
  • paused — The application is not currently visible to the user, not responding to user input, and running in the background. This is equivalent to onPause() in Android
  • resumed — The application is visible and responding to user input. This is equivalent to onPostResume() in Android
  • suspending — The application is suspended momentarily. This is equivalent to onStop in Android; it is not triggered on iOS as there is no equivalent event to map to on iOS

Feature tests have your back

Feature tests letting us know we have broken other features

This is great, I’ve made some pretty big changes, but I know what I need to fix to avoid any regression bugs.

The contract of the tests ensure the application still does what was intended by earlier features.

Feature testing Authentication

It proved extremely tricky to get the feature tests to pass, but it is worth having them in place, to ensure authentication works as planned.

The main issue was that the environment was loaded from file before the tests steps run, which meant we had already ran the logic to check authentication and redirect to the login screen.

After several failed attempts to change the environment through code I’ve opted to have a separate suite of feature tests :

Gherkin feature test config that loads the live config value file.

that load a different environment config file:

Live config value file

Its more maintenance but necessary as we are going to inject the config file on the integration and deployment builds to safe guard secret values.

Using events to work around difficult to implement, feature test steps

A few of the login feature steps are hard to test because the functionality will be implemented in the parent application DigestableMe that consumes the micro application DigestablePrologue :

When: Making an API request

This means we cannot navigate to the screens or make API requests in DigestablePrologue.

The is a coupling problem we can solve using the event bus.

The application now just listens for events and takes the appropriate action, allowing us to just raise events in the steps, in place of the actual navigation or API calls.

When: Making an API request Event: API Request

There are other advantages to using the EventBus that we can add later on, such as logging the events raised.

Discipline — Implementing feature steps

The most tedious part of using Flutter Gherkin is creating all the step methods, I’m going to attempt to automate this using the builder to create files with skeleton steps to fill out.

In the mean time I do this manually using the highlight in visual code to normalise the required steps e.g. this:

To this:

Given: Always on authentication
And: Not authenticated
And: authenticated

When: The application is started
When: The application awakes
When: Making an API request 
When: Displaying a restricted screen
When: Displaying an unrestricted screen

Then: The application routes to the 'Login Screen'
And: Records the current screen for redirection

And then add the skeleton methods to the step files

BackBurner

Route Authorisation

Attributed the route with a custom property Role, then can implement based non role, everyone for now, but can be extended.

Didn’t implement restricted screen with login/id/auth as its something different, its authorisation and route guards, do when required via GoRouter redirect as guard.

Links

Packages

Subscribe to view all the articles, or check out the code.

Flutter
Mobile
Mobile App Development
Developer
Security
Recommended from ReadMedium