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 Login
page. We will also replace
the "default" page (currently the Tab1
page) with a Start
page that will contain our startup logic.
Create basic shells for these pages.
Be sure to check the code for all three pages. All of the pages, especially the Start
page, are very minimal and that is intentional.
Update Routes
With the new pages in place, the routing needs to be fixed. The current routes are fairly flat with the tabs being defined in src/App.tsx
and all of the routes existing directly under the root route: /tab1
, /tab2
, /tab3
. The root (/
) currently redirects to /tab1
. As
we walk through this step, we will move the following code to its own component and modify it for our needs.
We want to have /
, /login
, and /unlock
with /
loading the Start
page. We also want to have a /tabs
route to collect the tabbed pages:
/tabs/tab1
, /tabs/tab2
, /tabs/tab3
.
Move the tabs from src/App.tsx
into their own component in src/components/Tabs.tsx
Adjust the paths to now be child paths.
Add the base-level routes to src/App.tsx
, including the parent /tabs
route.
Move the tabs from src/App.tsx
into their own component in src/components/Tabs.tsx
Adjust the paths to now be child paths.
Add the base-level routes to src/App.tsx
, including the parent /tabs
route.
The authentication
Utility Functions
Part of our startup strategy involves authentication. We will not really be performing authentication, but we will add the utility functions so that we have the infrastructure in place so we can later add authentication via a solution such as Auth Connect.
We will need to clear, store, and restore sessions from here.
For the login()
, store some fake session information in the vault.
For the logout()
, clear the session data from the vault.
To determine if the user is currently authenticated, make sure the session has been restored, and if there
is session data resolve true
, otherwise resolve false
.
Doing so requires a minor change to restoreSession()
so it resolves the currently stored session rather than void
.
We will need to clear, store, and restore sessions from here.
For the login()
, store some fake session information in the vault.
For the logout()
, clear the session data from the vault.
To determine if the user is currently authenticated, make sure the session has been restored, and if there
is session data resolve true
, otherwise resolve false
.
Doing so requires a minor change to restoreSession()
so it resolves the currently stored session rather than void
.
We now have a set of authentication
utility functions that we can use in the rest of our app. This also gives us the
stubs for the type of functions we need when we begin using an actual authentication solution such as
Auth Connect.
The Login
Page
Add a "Login" button to the Login
page.
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 the session-vault
Utility Functions
Remove the call to restoreSession()
at the end of the initializeVault()
function. We will now be restoring
the session via various flows within our application and should not do it during initialization any more.
The startup logic needs to determine if the vault is currently locked and provide a mechanism to unlock the vault
if it is locked. Add a sessionIsLocked()
function for use in the startup logic.
In the sessionIsLocked()
method, we do not get the reported state for vaults of type SecureStorage
or InMemory
because Identity Vault may 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 an empty component.
We start with a minimal, empty component for a page.
The Start
page navigates based on the current authentication status. If the user is authenticated, go to the main page
of the app, otherwise to go to the login page.
Note that determining if the user is authenticated will try to unlock the vault if it is currently locked.
Perform the previously defined navigation. If it fails it is likely because the user failed to unlock the vault, so navigate to the unlock page, giving the user an opportunity to try again.
We start with a minimal, empty component for a page.
The Start
page navigates based on the current authentication status. If the user is authenticated, go to the main page
of the app, otherwise to go to the login page.
Note that determining if the user is authenticated will try to unlock the vault if it is currently locked.
Perform the previously defined navigation. If it fails it is likely because the user failed to unlock the vault, so navigate to the unlock page, giving the user an opportunity to try again.
The UnlockPage
The Unlock
page 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 Tabs
component encapsulates all of the routes that the user is locked out
of when the vault is locked, so this is a natural location for this logic.
Upon locking, the session
will be set to null
. Update the Tabs
component to attempt an unlock any time the vault locks. If the
unlocking of the vault is canceled by the user, navigate to the Unlock
page. This process represents our component synchronizing with
an external system, so use a useEffect
hook.
Cleanup the Tab1
Page
There are several items in the Tab1
page that no longer make sense. 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.