Flutter Minimalist: App Account Management Part 1 (not Firebase)

Vaygeth (Abdulmohsen)
11 min readOct 16, 2021

Part 1 Persistence User Authentication with Cubit

This is part one of the series where we will implement user authentication that persists even when users close the app and handle additional cases when sessions expire and need to be renewed to reduce the number of users entering their credentials. The tutorials will be implemented in parts as follows:

  1. Part 1 — Implement User Authentication State with mocked API service (without making http request)
  2. Part 2 — Implement Sign In API service to make Http requests and discard the mocked API that was implemented in Part1 (stay tuned by following)
  3. Part 3 — Add User Sign Up feature with mocked service
  4. Part 4- Implement Sign Up API service to make Http requests and discard the mocked API

What’s this tutorial about:

1- Protect secured pages/screens/features that only sign in users to access

2- Implement Sign In Logic using OAuth2 or JWT session based authentication (AccessToken, RefreshToken…etc)

3- Dividing our project into several layered classes that handles it’s single responsibility (UI, States, Events,Services..etc as shown in the following figure)

4- Will implement our State management using Flutter Bloc specifically using Cubit states

5- Give our states a meaningful naming that describes different sign in conditions (like when invalid credentials, email not verified, invalid credentials…etc)

The Project Requirements

  • Landing Page
  • Profile Page
  • Sign In Page

The Landing Page

  • Contains a button that takes logged in users to their profile page
  • Only Authenticated users can navigate to Profile page otherwise they will be redirected to Sign In Page
Figure 1

The Profile Page

  • Is to show users private information, and is protected by preventing unauthenticated to access it
  • For simplicity we are going to show only User ID and access token
Figure 2

The Sign In Page

  • Where users are able to input their credentials and get authenticated
Figure 3

Project Design Pattern Overview

Figure 4

Packages and prerequisites

Before we begin, let’s add the required packages like shown in following image

Figure 5

In order to store private information on the devices using flutter_secure_storage make sure we increase the minimum required Android SDK in Android build.grade as shown

Figure 6

App User Authentication Flow

App Auth service is responsible for calling requests to our backend service/server APIs to inform our app that the user is authenticated state, or unauthenticated state (and other states)

Figure 7

Coding Time

For developing the project, I will mainly highlight the important pieces of the code and will not include all the details in upcoming implementation, however the full source code is available on github repo that you can reference.

API/Repositories

We will start implementing our mock up API class here since it doesn’t have any dependencies on other classes, and is the responsible class to request data from our backend (in part 1 we are creating a mockup for this class and part 2 of the tutorial we will implement the real API repository making Http Requests to our backend)

Create a file named api_repository.dart under repository/api folders as follows

API Repository Methods

SignIn()

  • Responsible for authenticating the user

VerifyToken()

  • Will cross check the current Access Token in device still valid

RenewAccessToken()

  • Responsible to renew User Authentication data by having new Authentication data like Access Token

Now API repositories need some classes that are not yet created in order to proceed with making http requests in the future, so let’s create them under models/ folder

sign_in.dart model

renew_access_token.dart model

authentication_data.dart model

Now implementing our concrete API Repository (in this part is a mock up class) so we are gonna name it FakeAPIRepository and implement the abstract APIRepository and override the required methods in a mock-up manner. Therefore we are gonna put static values for email,password, access & refresh token and auth data.

Create fake_account_api_repository.dart under repository/api/ folders

Bloc/Cubit State Management Classes

Now this is the important parts where our States Management events and states are responsible pieces to orchestrate user Events like Sign In and communicate this to our API Repository to get authenticated, so let’s define and plan what are the possible User Authentication states

States

Is the end result/output of an event either triggered by the system or UI events like button tapping to sign in.

UserAuthenticationInitial

  • This states is emitted when the app is first opened and notify our app that it needs to try and validate our stored user authentication data (if they exist) like access token with our API Repo

UserAuthenticating

  • The defines the app is in authenticating states like waiting to server to confirm or reply whether the sign in is successful or not
  • Therefore we can use this state to show the users that the app is processing the data and show a loading indicator

UserAuthenticated

  • This when our API returns a successful Sign In and the user credentials were valid
  • The app is considered the user is authenticated and allow him to access pages/features that requires elevated privileges
  • This state will wrap our session data (AuthenticationData)

UserAuthenticationInvalidCredentials

  • This when our API returns a failed Sign In and the user credentials were invalid
  • We defined this State in order to show the user email & password that he entered were incorrect

UserAuthenticationEmailNotConfirmed

  • This just an extra state that some developers want to handle, for example sometimes users Sign Up to an app/website and the service requires to verify the user email in order to Sign In successfully

UserAuthenticationError

  • The purpose of this state is to notify the app the authentication for some reason didn’t complete or return other generic errors, for example back API service is down, service timeout…etc

UserUnauthenticated

  • This state can be emitted for any handled exceptions,cases when app failed to authenticate with the service, for example when user first open the app and the app tried to fetch stored session data like access token from devices (means the user didn’t sign in before or forced delete the app data)

Create a file named user_authentication_state.dart as follows under cubit/ folder

Cubit

Is the responsible class to coordinate both user events and system events in order to communicate with services and update the app state

Create a file named user_authentication_cubit.dart as follows under cubit/ folder

The UserAuthenticationCubit class contains different methods that however we will focus on the ones that will

validateAuthentication()

  • There’s several nested conditions here, first the method tries to fetch existing stored Authentication info (access,refresh tokens and user id) stored on device secured storage
  • If there’s no stored Authentication Data then it will emit the UserUnauthenticated and is considered not logged-in.
  • If info are stored then it will make API call to our backend (in this case fake service) using AccountRepository.validateToken(accessToken) sending it the Access Token to verify its validity
  • If the service returns a successful response (for example Http 200) then the method emits and changes the state to UserAuthenticated (logged-in)
  • Else the service tries to renew the session by having a new Access Token by calling AccountService.renewAccessToken(refreshtoken)
  • If service returns successful Http status 200 then it will store or replace the existing Authentication Data with the new one into the device secure storage and emit/notify that the user is UserAuthenticated
  • Else if service is not successful (for whatever reason) the method will clear stored Authentication Data and emits UserUnauthenticated state
  • Notice by default, I usually fire & call this method upon creating an instance of the UserAuthenticationCubit in order to validate the user info without waiting for it to finish, because are updating UI states reactively.
  • You can use this method whenever suitable, however I like to fire it upon instantiating the Cubit because it only happens once when the user opens the application

logoutUser()

  • This is a simple method that deletes all user stored data (access, refresh tokens and user id) and emits UserUnauthenticated state

signInUser(signInInput)

  • This is self explanatory method, this method will only be used when users input their credentials in the Sign In Page and click Login, however notice there’s several scenarios (States) that need to be considered and handled by our app UI to have further descriptive and informative error handling to show or redirect users to corrective behaviour according to the business requirements. For the simplicity of this tutorial, I’ve limited this to several following cases:
  • Users enter invalid credentials, this happens when our backend service returns Http Status 400 (Bad request) and returns an object (BusinessError.dart) in the response with containing error code 123
  • Then the event will emit UserAuthenticationInvalidCredentials
  • Based on that error code, the state will be emitted, and our app will show error message notifying users that they entered invalid credentials
  • Users enters correct credentials but the business requires the user email to be verified in order to sign-in successfully (to receive tha Authentication Data), this happens when our backend service returns Http Status 400 (Bad request) and returns an object (BusinessError.dart) in the response with containing error code 321
  • Then the event will emit UserAuthenticationEmailNotConfirmed
  • Based on that error code, the state will be emitted, and our app will show a message that they need to check their email in order to verify and confirm email connected to heir account
  • Users enter invalid/valid credentials but the operations failed for some reasons, for example
  1. Mobile Internet went down
  2. Server is not reachable (network, url no longer valid)
  3. Network Timed Out
  4. Other may other odd cases

Based on the above, we can show a generic message like the service is not available at this time, and they need to try again later on.

Time to Widget

The overall strategy for our app, is to give good user experience where the app will allow different type of users to use the app in both guest users (unauthenticated) and logged-in (authenticated) users to access various parts of the application and only limit guest users for accessing certain features (widgets/pages). So in this tutorial we will give the following permissions as follows

Guest Users

  • Allowed access to Landing Page
  • Allowed access to Sign In Page

Logged-in Users

  • Allowed access to Landing Page
  • Allowed access to Profile Page

As per above, we notice the guest users can’t access Profile page (to view user info) and the logged-in users can’t access the Sign In page (naturally since they are already authenticated), therefore we can conclude that Profile page & Sign In page are mutually exclusive (they can’t coexists or is accessible for the same user).

To achieve the above, we need to somehow protect and coordinate to make sure these features (Profile & Sign In pages) should never overlap for the same user state (authenticated or unauthenticated)

The solutions

Is to wrap any feature that may require elevated access (authenticated) that coordinates based on the user’s current state and also reduce code redundancy (DRY i.e Don’t Repeat Yourself), and to do so I came up with a Wrapper Widget that wraps features/pages that we want to protect, so I named it ProtectionWidget that host the UserAuthenticationCubit & UserAuthenticationStates and build the appropriate widget accordingly.

Here’s the High Level Flow

Figure 8

Let’s code our routes first and add the pages afterward

Routes Class

This class simply defines the route names and maps to what page. Note for simplicity, I’m going to use navigator 1.0 without any plugins, where navigator 2.0 will be full of boilerplate code, so I suggest to use Flutter packages like to stay with the latest navigation version and make things easier and supports different platforms like named routes for Web platform when user manually enters the URL manually and navigate to a specific page.

Checkout the following packages

Nevertheless, here’s our navigator 1.0 class. Create file named routes.dart as follows under routes/ folder

Now we create the required pages/widget that Routes Class needs

Landing Page

Create landing_page.dart file as follows under pages/ folder

Profile Page

Create profile_page.dart file as follows under pages/ folder

Sign In Page

Create sign_in_page.dart file as follows under pages/ folder

Main Widget

Now let’s hook up our app to be functional without any state aware logic in the main.dart

For now our app will be fully functional but is not state aware yet, so to

Protection Widget

Now the important bit of this tutorial that wire everything we wrote, is to create a widget that wraps the user authentication cubit and helps coordinate which UI should be built according to user authentication state.

Create a file named protected_widget.dart under a folder named widgets/

You will notice that the widget accepts 2 required parameters

onAuthenticated builder widget

  • This builder widget is returned when the user is authenticated, along with user AuthenticationData

onUnauthenticatedChild widget

  • This widget will be built when the user is not UserAuthenticatedState, that means any of the other states
  • In this tutorial we will only use it for showing the SignInPage widget
  • Note you can handle extra states and return your desired widget/behaviour like handling UserAuthenticating state and show progress bar

Now let’s make our application be aware of user authentication states

MyApp widget

Wrap Material app with MultiBlocProvider in order to make the UserAuthentication state can be watched/read from any part of the widget tree

SignInPage

  • Include the UserAuthenticationCubit in the widget in order to allow it to handle user sign in event when sign in button callback is invoked
  • SignInForm also need to be aware of the different user authentication states especially the failed cases as follow UserAuthenticationError, UserAuthenticationEmailNotConfirmed, and UserAuthenticationInvalidCredentials in order to show the error message for the different cases

ProfilePage

Finally wrap our build method with the created ProtectedWidget as follows:

  • Passing the SignInPage to onUnauthenticatedChild parameter
  • For onAuthenticated we keep the original profile widgets that was previously created along displaying some user authentication data that was returned from the builder like access token and user id
  • Finally include and watch UserAuthenticationCubit at the beginning of the build method and handle the logout event when user table the logout icon button, in order to sign-off the user and direct him to the SignInPage

This concludes part 1 of the series, in the next part we will replace the mocked and fake API with a real one to make http requests. Stay tuned and follow me to stay up today for coming parts

For the source control of this part click here

--

--

Vaygeth (Abdulmohsen)

I’m a software developer & UI/UX designer who want to share my experience with fellow developers. abdulmohsen.co https://www.patreon.com/vaygeth