Handling Application Startup
Overview
When we created the application for the getting started tutorial we made sure Identity Vault was properly initialized before we used it. However, we just jumped right into the first tab of the main part of the app. This is not a realistic experience for our users. Let's implement something more realistic.
The Login Page
Our application currently just starts right up in the application itself and we have a button that the user can press to store the authentication information in the vault. This is not realistic. Our application should have a page where the user logs in.
In our case, this will still just be a button that the user presses to fake a log in, but we are getting a step closer to an actual flow by having the login page.
The Startup Flow
When our application starts, the session can be in one of the following states:
- Locked:
- With valid authentication tokens.
- With invalid authentication tokens.
- Not logged in.
If the application is locked, the application shall give the user the opportunity to unlock the vault. If the unlock fails, the user shall be given the option to either try again or to clear the session data and log in again.
If the user unlocks the vault and the resulting authentication information is valid, the first tab shall be loaded. For our tutorial application, if we have session data the session is, by definition, valid.
If the user unlocks the vault and the resulting authentication information is expired, the login page shall be loaded. Having expired or otherwise invalid authentication information is not technically possible in our tutorial application, but we will code for it none the less.
If the user is not logged in, the login page shall be loaded.
The Unlock on Resume Flow
It is also possible for the vault to lock while the application is in the background. In reality, Identity Vault detects
that the application has resumed and will lock the vault if more than lockAfterBackgrounded
milliseconds have passed.
One flow we may want to implement in this case is to give the user the opportunity to unlock the vault immediately upon
receiving an onLock
event for the vault. In this scenario, the user stays on whichever page they were on when the app locked.
If the user cancels the unlock, however, we should navigate to an "Unlock" page where we give the user various options as to what they would like to do next (these are usually to attempt to unlock again, or to log out and back in to the application).
If you are using lockAfterBackgrounded
do not interact with the vault in a resume
event handler. Doing so will cause
a race condition resulting in indeterminate behavior. Always manage the state of the vault through the onLock
and onUnlock
event handlers, never the resume
event.
Let's Code
We will build upon the application we created in the getting started tutorial in order to implement the basic application startup workflow along with the unlocking upon resume flow.
If you have the code from when you performed the getting started tutorial, then you are good to go. If you need the code you can make a copy from our GitHub repository.
Create the New Pages
In order to implement our startup and authentication strategies, we need to have a LoginPage
. We will also replace
the "default" page (currently the Tab1Page
) with a StartPage
that will contain our startup logic.
Create basic shells for these pages.
Be sure to check the code for all three pages. The StartPage
and UnlockPage
are very minimal and that is intentional.
Update Routes
With the new pages in place, the routing needs to be fixed. The application's routing scheme has two levels: a base
page level and a sub-page level. As such, each of our routes has one of the following formats: /base-page
or
/base-page/sub-page
.
At the base page level, we want to have four different pages: TabsPage
, LoginPage
, StartPage
, and UnlockPage
. We also want
the default route (/
) to be the StartPage
. Update the src/router/index.ts
accordingly.
Currently we only have routes for the TabsPage
and its children.
Lazy-load the TabsPage
.
Eager-load the StartPage
and change the /
to redirect to it.
Lazy-load the LoginPage
and UnlockPage
.
Currently we only have routes for the TabsPage
and its children.
Lazy-load the TabsPage
.
Eager-load the StartPage
and change the /
to redirect to it.
Lazy-load the LoginPage
and UnlockPage
.
The useAuthentication
Composable
Part of our startup strategy involves authentication. We will not really be performing authentication, but we will add the composable so that we have the infrastructure in place so we can later add authentication via a solution such as Auth Connect.
Start with an empty composable.
Get the functions and data that we need from the useSessionVault
composable.
The user needs to be able to log in. Since we do not yet have an authentication strategy, we will store a fake session.
For the logout()
, just clear the stored session.
To determine if the user is authenticated, check for a stored session.
Start with an empty composable.
Get the functions and data that we need from the useSessionVault
composable.
The user needs to be able to log in. Since we do not yet have an authentication strategy, we will store a fake session.
For the logout()
, just clear the stored session.
To determine if the user is authenticated, check for a stored session.
We now have a useAuthentication
composable that we can use in the rest of our app. This also gives us the hooks we need when we begin
using an actual authentication solution such as Auth Connect.
The LoginPage
Add a "Login" button to the LoginPage
.
When the button is pressed the following tasks are performed:
- Attempt to log in.
- If the login succeeds, go to the
Tab1Page
. - If the login fails we will just log it for now. When actual authentication is implemented this may be a good place to display a "Login Failed" message, but that is beyond the scope of this tutorial.
Update useSessionVault
The startup logic needs to determine if the vault is currently locked and provide a mechanism to unlock the vault
if it is locked. Update the useSessionVault
composable to provide unlockSession()
and sessionIsLocked()
functions.
In the sessionIsLocked()
method, we do not get the reported state for vaults of type SecureStorage
or InMemory
because Identity Vault will report them as "locked" even though they logically cannot lock. This is a long standing
quirk with Identity Vault that would be a breaking change to fix.
The StartPage
When the application starts, it will be in one of two states:
- Logged out: the user needs to be redirected to the login page.
- Logged in and locked: the user should be given the opportunity to unlock the app.
As such, the StartPage
does not contain any real user interaction so the UI can be minimal. You could show a spinner,
or your company logo, or something like that. For our simple app we will just show a blank page.
We start with a minimal page.
Upon entry the page may attempt an unlock operation, and it will navigate somewhere based on the current authentication state.
If the vault is locked try to unlock it. If the unlock fails (usually due to the user canceling the attempt), then navigate to the "Unlock" page so the user can decide the best course of action.
If the vault is unlocked (either due to not having been locked or the user successfully unlocking it), determine if we should navigate to
the LoginPage
or the Tab1Page
based on the current authentication status.
We start with a minimal page.
Upon entry the page may attempt an unlock operation, and it will navigate somewhere based on the current authentication state.
If the vault is locked try to unlock it. If the unlock fails (usually due to the user canceling the attempt), then navigate to the "Unlock" page so the user can decide the best course of action.
If the vault is unlocked (either due to not having been locked or the user successfully unlocking it), determine if we should navigate to
the LoginPage
or the Tab1Page
based on the current authentication status.
The UnlockPage
The UnlockPage
is visited after the user chooses to cancel the unlocking of the vault elsewhere in the application. The purpose of this
page is to give the user reasonable options for what they can try next: either re-try the unlock or completely redo their login.
We start with a minimal page.
At this point, the user has two options: try to unlock the vault again or redo the login.
To redo the login, logout and then navigate to the Login Page.
Try to unlock the vault and navigate to the first page of the app if successful.
We start with a minimal page.
At this point, the user has two options: try to unlock the vault again or redo the login.
To redo the login, logout and then navigate to the Login Page.
Try to unlock the vault and navigate to the first page of the app if successful.
Handle a Vault Locking In-App
The vault will lock upon resume if the application has been in the background more than lockAfterBackgrounded
milliseconds.
In our case, that is 2000 milliseconds. When this happens, it would be nice to initiate an initial unlock attempt without navigating
the user away from wherever they are in the app. The App
component is a good centralized location from which to handle this logic.
Upon locking, the session
will be set to null
. Update the App
component to watch to the session
. When the session
changes, check the vault's lock status. If the vault is locked try to unlock the vault. If the user cancels this, an exception will be
thrown. In that case, navigate to the UnlockPage
.
Cleanup the Tab1Page
There are several items in the Tab1Page
that no longer make sense, however, and now is a good time to clean those up.
Here is a synopsis of what needs to be cleaned up:
- Remove the "Store" button and all code associated with it.
- Change the "Clear" button to a "Logout" button and update the click handler accordingly.
- Remove the "Lock" and "Unlock" buttons and all code associated with them.
Cleaning this all up is left as an exercise to the reader but we provide the completed code here for you to compare against.
Next Steps
In this tutorial, we created a basic application startup workflow. This is an example of a good workflow, but it is not the only potential flow. Tailor it for your application.