Skip to main content
Version: 5.0

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:

  1. Locked:
    1. With valid authentication tokens.
    2. With invalid authentication tokens.
  2. 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).

important

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 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.

src/views/LoginPage.vue
src/views/StartPage.vue
src/views/UnlockPage.vue

_20
<template>
_20
<ion-page>
_20
<ion-header>
_20
<ion-toolbar>
_20
<ion-title>Login</ion-title>
_20
</ion-toolbar>
_20
</ion-header>
_20
<ion-content :fullscreen="true">
_20
<ion-header collapse="condense">
_20
<ion-toolbar>
_20
<ion-title size="large">Login</ion-title>
_20
</ion-toolbar>
_20
</ion-header>
_20
</ion-content>
_20
</ion-page>
_20
</template>
_20
_20
<script setup lang="ts">
_20
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/vue';
_20
</script>

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.

src/router/index.ts

_39
import { createRouter, createWebHistory } from '@ionic/vue-router';
_39
import { RouteRecordRaw } from 'vue-router';
_39
import TabsPage from '../views/TabsPage.vue';
_39
_39
const routes: Array<RouteRecordRaw> = [
_39
{
_39
path: '/',
_39
redirect: '/tabs/tab1',
_39
},
_39
{
_39
path: '/tabs/',
_39
component: TabsPage,
_39
children: [
_39
{
_39
path: '',
_39
redirect: '/tabs/tab1',
_39
},
_39
{
_39
path: 'tab1',
_39
component: () => import('@/views/Tab1Page.vue'),
_39
},
_39
{
_39
path: 'tab2',
_39
component: () => import('@/views/Tab2Page.vue'),
_39
},
_39
{
_39
path: 'tab3',
_39
component: () => import('@/views/Tab3Page.vue'),
_39
},
_39
],
_39
},
_39
];
_39
_39
const router = createRouter({
_39
history: createWebHistory(import.meta.env.BASE_URL),
_39
routes,
_39
});
_39
_39
export default router;

Currently we only have routes for the TabsPage and its children.

src/router/index.ts

_38
import { createRouter, createWebHistory } from '@ionic/vue-router';
_38
import { RouteRecordRaw } from 'vue-router';
_38
_38
const routes: Array<RouteRecordRaw> = [
_38
{
_38
path: '/',
_38
redirect: '/tabs/tab1',
_38
},
_38
{
_38
path: '/tabs/',
_38
component: () => import('@/views/TabsPage.vue'),
_38
children: [
_38
{
_38
path: '',
_38
redirect: '/tabs/tab1',
_38
},
_38
{
_38
path: 'tab1',
_38
component: () => import('@/views/Tab1Page.vue'),
_38
},
_38
{
_38
path: 'tab2',
_38
component: () => import('@/views/Tab2Page.vue'),
_38
},
_38
{
_38
path: 'tab3',
_38
component: () => import('@/views/Tab3Page.vue'),
_38
},
_38
],
_38
},
_38
];
_38
_38
const router = createRouter({
_38
history: createWebHistory(import.meta.env.BASE_URL),
_38
routes,
_38
});
_38
_38
export default router;

Lazy-load the TabsPage.

src/router/index.ts

_43
import { createRouter, createWebHistory } from '@ionic/vue-router';
_43
import { RouteRecordRaw } from 'vue-router';
_43
import StartPage from '@/views/StartPage.vue';
_43
_43
const routes: Array<RouteRecordRaw> = [
_43
{
_43
path: '/',
_43
redirect: '/start',
_43
},
_43
{
_43
path: '/start',
_43
component: StartPage,
_43
},
_43
{
_43
path: '/tabs/',
_43
component: () => import('@/views/TabsPage.vue'),
_43
children: [
_43
{
_43
path: '',
_43
redirect: '/tabs/tab1',
_43
},
_43
{
_43
path: 'tab1',
_43
component: () => import('@/views/Tab1Page.vue'),
_43
},
_43
{
_43
path: 'tab2',
_43
component: () => import('@/views/Tab2Page.vue'),
_43
},
_43
{
_43
path: 'tab3',
_43
component: () => import('@/views/Tab3Page.vue'),
_43
},
_43
],
_43
},
_43
];
_43
_43
const router = createRouter({
_43
history: createWebHistory(import.meta.env.BASE_URL),
_43
routes,
_43
});
_43
_43
export default router;

Eager-load the StartPage and change the / to redirect to it.

src/router/index.ts

_51
import { createRouter, createWebHistory } from '@ionic/vue-router';
_51
import { RouteRecordRaw } from 'vue-router';
_51
import StartPage from '@/views/StartPage.vue';
_51
_51
const routes: Array<RouteRecordRaw> = [
_51
{
_51
path: '/',
_51
redirect: '/start',
_51
},
_51
{
_51
path: '/start',
_51
component: StartPage,
_51
},
_51
{
_51
path: '/login',
_51
component: () => import('@/views/LoginPage.vue'),
_51
},
_51
{
_51
path: '/unlock',
_51
component: () => import('@/views/UnlockPage.vue'),
_51
},
_51
{
_51
path: '/tabs/',
_51
component: () => import('@/views/TabsPage.vue'),
_51
children: [
_51
{
_51
path: '',
_51
redirect: '/tabs/tab1',
_51
},
_51
{
_51
path: 'tab1',
_51
component: () => import('@/views/Tab1Page.vue'),
_51
},
_51
{
_51
path: 'tab2',
_51
component: () => import('@/views/Tab2Page.vue'),
_51
},
_51
{
_51
path: 'tab3',
_51
component: () => import('@/views/Tab3Page.vue'),
_51
},
_51
],
_51
},
_51
];
_51
_51
const router = createRouter({
_51
history: createWebHistory(import.meta.env.BASE_URL),
_51
routes,
_51
});
_51
_51
export default router;

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.

src/router/index.ts

_39
import { createRouter, createWebHistory } from '@ionic/vue-router';
_39
import { RouteRecordRaw } from 'vue-router';
_39
import TabsPage from '../views/TabsPage.vue';
_39
_39
const routes: Array<RouteRecordRaw> = [
_39
{
_39
path: '/',
_39
redirect: '/tabs/tab1',
_39
},
_39
{
_39
path: '/tabs/',
_39
component: TabsPage,
_39
children: [
_39
{
_39
path: '',
_39
redirect: '/tabs/tab1',
_39
},
_39
{
_39
path: 'tab1',
_39
component: () => import('@/views/Tab1Page.vue'),
_39
},
_39
{
_39
path: 'tab2',
_39
component: () => import('@/views/Tab2Page.vue'),
_39
},
_39
{
_39
path: 'tab3',
_39
component: () => import('@/views/Tab3Page.vue'),
_39
},
_39
],
_39
},
_39
];
_39
_39
const router = createRouter({
_39
history: createWebHistory(import.meta.env.BASE_URL),
_39
routes,
_39
});
_39
_39
export default router;

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.

src/composables/authentication.ts

_10
export const useAuthentication = (): any => ({});

Start with an empty composable.

src/composables/authentication.ts

_10
import { useSessionVault } from './session-vault';
_10
_10
const { clearSession, getSession, session, storeSession } = useSessionVault();
_10
_10
export const useAuthentication = (): any => ({});

Get the functions and data that we need from the useSessionVault composable.

src/composables/authentication.ts

_14
import { useSessionVault } from './session-vault';
_14
_14
const { clearSession, getSession, session, storeSession } = useSessionVault();
_14
_14
const login = (): Promise<void> =>
_14
storeSession({
_14
email: 'test@ionic.io',
_14
firstName: 'Tessa',
_14
lastName: 'Testsmith',
_14
accessToken: '4abf1d79-143c-4b89-b478-19607eb5ce97',
_14
refreshToken: '565111b6-66c3-4527-9238-6ea2cc017126',
_14
});
_14
_14
export const useAuthentication = (): any => ({ login });

The user needs to be able to log in. Since we do not yet have an authentication strategy, we will store a fake session.

src/composables/authentication.ts

_16
import { useSessionVault } from './session-vault';
_16
_16
const { clearSession, getSession, session, storeSession } = useSessionVault();
_16
_16
const login = (): Promise<void> =>
_16
storeSession({
_16
email: 'test@ionic.io',
_16
firstName: 'Tessa',
_16
lastName: 'Testsmith',
_16
accessToken: '4abf1d79-143c-4b89-b478-19607eb5ce97',
_16
refreshToken: '565111b6-66c3-4527-9238-6ea2cc017126',
_16
});
_16
_16
const logout = (): Promise<void> => clearSession();
_16
_16
export const useAuthentication = (): any => ({ login, logout });

For the logout(), just clear the stored session.

src/composables/authentication.ts

_25
import { useSessionVault } from './session-vault';
_25
_25
const { clearSession, getSession, session, storeSession } = useSessionVault();
_25
_25
const login = (): Promise<void> =>
_25
storeSession({
_25
email: 'test@ionic.io',
_25
firstName: 'Tessa',
_25
lastName: 'Testsmith',
_25
accessToken: '4abf1d79-143c-4b89-b478-19607eb5ce97',
_25
refreshToken: '565111b6-66c3-4527-9238-6ea2cc017126',
_25
});
_25
_25
const logout = (): Promise<void> => clearSession();
_25
_25
const isAuthenticated = async (): Promise<boolean> => {
_25
await getSession();
_25
return !!session.value;
_25
};
_25
_25
export const useAuthentication = (): any => ({
_25
isAuthenticated,
_25
login,
_25
logout,
_25
});

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.

src/composables/authentication.ts

_10
export const useAuthentication = (): any => ({});

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

The login page simply includes a "Login" button.

src/views/LoginPage.vue

_38
<template>
_38
<ion-page>
_38
<ion-header>
_38
<ion-toolbar>
_38
<ion-title>Login</ion-title>
_38
</ion-toolbar>
_38
</ion-header>
_38
<ion-content :fullscreen="true">
_38
<ion-header collapse="condense">
_38
<ion-toolbar>
_38
<ion-title size="large">Login</ion-title>
_38
</ion-toolbar>
_38
</ion-header>
_38
_38
<ion-list>
_38
<ion-item>
_38
<ion-label>
_38
<ion-button expand="block">Login</ion-button>
_38
</ion-label>
_38
</ion-item>
_38
</ion-list>
_38
</ion-content>
_38
</ion-page>
_38
</template>
_38
_38
<script setup lang="ts">
_38
import {
_38
IonButton,
_38
IonContent,
_38
IonHeader,
_38
IonItem,
_38
IonLabel,
_38
IonList,
_38
IonPage,
_38
IonTitle,
_38
IonToolbar,
_38
} from '@ionic/vue';
_38
</script>

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.
src/views/LoginPage.vue

_52
<template>
_52
<ion-page>
_52
<ion-header>
_52
<ion-toolbar>
_52
<ion-title>Login</ion-title>
_52
</ion-toolbar>
_52
</ion-header>
_52
<ion-content :fullscreen="true">
_52
<ion-header collapse="condense">
_52
<ion-toolbar>
_52
<ion-title size="large">Login</ion-title>
_52
</ion-toolbar>
_52
</ion-header>
_52
_52
<ion-list>
_52
<ion-item>
_52
<ion-label>
_52
<ion-button expand="block" @click="handleLogin">Login</ion-button>
_52
</ion-label>
_52
</ion-item>
_52
</ion-list>
_52
</ion-content>
_52
</ion-page>
_52
</template>
_52
_52
<script setup lang="ts">
_52
import {
_52
IonButton,
_52
IonContent,
_52
IonHeader,
_52
IonItem,
_52
IonLabel,
_52
IonList,
_52
IonPage,
_52
IonTitle,
_52
IonToolbar,
_52
} from '@ionic/vue';
_52
import { useRouter } from 'vue-router';
_52
import { useAuthentication } from '@/composables/authentication';
_52
_52
const { login } = useAuthentication();
_52
const router = useRouter();
_52
_52
const handleLogin = async () => {
_52
try {
_52
await login();
_52
router.replace('/tabs/tab1');
_52
} catch (error: unknown) {
_52
console.error('Failed to log in', error);
_52
}
_52
};
_52
</script>

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.

src/composables/session-vault.ts

_94
import { useVaultFactory } from '@/composables/vault-factory';
_94
import { Session } from '@/models/session';
_94
import {
_94
BrowserVault,
_94
DeviceSecurityType,
_94
IdentityVaultConfig,
_94
Vault,
_94
VaultType,
_94
} from '@ionic-enterprise/identity-vault';
_94
import { ref } from 'vue';
_94
_94
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_94
_94
const { createVault } = useVaultFactory();
_94
const vault: Vault | BrowserVault = createVault();
_94
const session = ref<Session | null>(null);
_94
_94
const initializeVault = async (): Promise<void> => {
_94
try {
_94
await vault.initialize({
_94
key: 'io.ionic.gettingstartediv',
_94
type: VaultType.SecureStorage,
_94
deviceSecurityType: DeviceSecurityType.None,
_94
lockAfterBackgrounded: 2000,
_94
});
_94
} catch (e: unknown) {
_94
await vault.clear();
_94
await updateUnlockMode('SecureStorage');
_94
}
_94
_94
vault.onLock(() => (session.value = null));
_94
};
_94
_94
const storeSession = async (s: Session): Promise<void> => {
_94
vault.setValue('session', s);
_94
session.value = s;
_94
};
_94
_94
const getSession = async (): Promise<void> => {
_94
session.value = await vault.getValue<Session>('session');

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.

src/views/StartPage.vue

_10
<template>
_10
<ion-page>
_10
<ion-content> </ion-content>
_10
</ion-page>
_10
</template>
_10
_10
<script setup lang="ts">
_10
import { IonContent, IonPage } from '@ionic/vue';
_10
</script>

We start with a minimal page.

src/views/StartPage.vue

_20
<template>
_20
<ion-page>
_20
<ion-content> </ion-content>
_20
</ion-page>
_20
</template>
_20
_20
<script setup lang="ts">
_20
import { IonContent, IonPage, onIonViewDidEnter } from '@ionic/vue';
_20
_20
const performNavigation = async (): Promise<void> => {
_20
};
_20
_20
const performUnlock = async (): Promise<void> => {
_20
};
_20
_20
onIonViewDidEnter(async () => {
_20
await performUnlock();
_20
await performNavigation();
_20
});
_20
</script>

Upon entry the page may attempt an unlock operation, and it will navigate somewhere based on the current authentication state.

src/views/StartPage.vue

_32
<template>
_32
<ion-page>
_32
<ion-content> </ion-content>
_32
</ion-page>
_32
</template>
_32
_32
<script setup lang="ts">
_32
import { useSessionVault } from '@/composables/session-vault';
_32
import { IonContent, IonPage, onIonViewDidEnter } from '@ionic/vue';
_32
import { useRouter } from 'vue-router';
_32
_32
const { sessionIsLocked, unlockSession } = useSessionVault();
_32
const router = useRouter();
_32
_32
const performNavigation = async (): Promise<void> => {
_32
};
_32
_32
const performUnlock = async (): Promise<void> => {
_32
if (await sessionIsLocked()) {
_32
try {
_32
await unlockSession();
_32
} catch (err: unknown) {
_32
router.replace('/unlock');
_32
}
_32
}
_32
};
_32
_32
onIonViewDidEnter(async () => {
_32
await performUnlock();
_32
await performNavigation();
_32
});
_32
</script>

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.

src/views/StartPage.vue

_41
<template>
_41
<ion-page>
_41
<ion-content> </ion-content>
_41
</ion-page>
_41
</template>
_41
_41
<script setup lang="ts">
_41
import { useAuthentication } from '@/composables/authentication';
_41
import { useSessionVault } from '@/composables/session-vault';
_41
import { IonContent, IonPage, onIonViewDidEnter } from '@ionic/vue';
_41
import { useRouter } from 'vue-router';
_41
_41
const { isAuthenticated } = useAuthentication();
_41
const { sessionIsLocked, unlockSession } = useSessionVault();
_41
const router = useRouter();
_41
_41
const performNavigation = async (): Promise<void> => {
_41
if (!(await sessionIsLocked())) {
_41
if (await isAuthenticated()) {
_41
router.replace('/tabs/tab1');
_41
} else {
_41
router.replace('/login');
_41
}
_41
}
_41
};
_41
_41
const performUnlock = async (): Promise<void> => {
_41
if (await sessionIsLocked()) {
_41
try {
_41
await unlockSession();
_41
} catch (err: unknown) {
_41
router.replace('/unlock');
_41
}
_41
}
_41
};
_41
_41
onIonViewDidEnter(async () => {
_41
await performUnlock();
_41
await performNavigation();
_41
});
_41
</script>

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.

src/views/StartPage.vue

_10
<template>
_10
<ion-page>
_10
<ion-content> </ion-content>
_10
</ion-page>
_10
</template>
_10
_10
<script setup lang="ts">
_10
import { IonContent, IonPage } from '@ionic/vue';
_10
</script>

The UnlockPage

The UnlockPage is only visited if the user chooses to cancel the unlocking of the vault. 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.

src/views/UnlockPage.vue

_10
<template>
_10
<ion-page>
_10
<ion-content> </ion-content>
_10
</ion-page>
_10
</template>
_10
_10
<script setup lang="ts">
_10
import { IonContent, IonPage } from '@ionic/vue';
_10
</script>

We start with a minimal page.

src/views/UnlockPage.vue

_35
<template>
_35
<ion-page>
_35
<ion-header>
_35
<ion-toolbar>
_35
<ion-title>
_35
Unlock
_35
</ion-title>
_35
</ion-toolbar>
_35
</ion-header>
_35
<ion-content class="ion-padding">
_35
<ion-list>
_35
<ion-item>
_35
<ion-label>
_35
<ion-button expand="block" @click="unlock" data-testid="unlock">Unlock</ion-button>
_35
</ion-label>
_35
</ion-item>
_35
<ion-item>
_35
<ion-label>
_35
<ion-button expand="block" @click="redoLogin" data-testid="redo-login">Redo Login</ion-button>
_35
</ion-label>
_35
</ion-item>
_35
</ion-list>
_35
</ion-content>
_35
</ion-page>
_35
</template>
_35
_35
<script setup lang="ts">
_35
import { IonButton, IonContent, IonHeader, IonItem, IonLabel, IonList, IonPage, IonTitle, IonToolbar } from '@ionic/vue';
_35
_35
const redoLogin = async (): Promise<void> => {
_35
};
_35
_35
const unlock = async (): Promise<void> => {
_35
};
_35
</script>

At this point, the user has two options: try to unlock the vault again or redo the login.

src/views/UnlockPage.vue

_42
<template>
_42
<ion-page>
_42
<ion-header>
_42
<ion-toolbar>
_42
<ion-title>
_42
Unlock
_42
</ion-title>
_42
</ion-toolbar>
_42
</ion-header>
_42
<ion-content class="ion-padding">
_42
<ion-list>
_42
<ion-item>
_42
<ion-label>
_42
<ion-button expand="block" @click="unlock" data-testid="unlock">Unlock</ion-button>
_42
</ion-label>
_42
</ion-item>
_42
<ion-item>
_42
<ion-label>
_42
<ion-button expand="block" @click="redoLogin" data-testid="redo-login">Redo Login</ion-button>
_42
</ion-label>
_42
</ion-item>
_42
</ion-list>
_42
</ion-content>
_42
</ion-page>
_42
</template>
_42
_42
<script setup lang="ts">
_42
import { useAuthentication } from '@/composables/authentication';
_42
import { IonButton, IonContent, IonHeader, IonItem, IonLabel, IonList, IonPage, IonTitle, IonToolbar } from '@ionic/vue';
_42
import { useRouter } from 'vue-router';
_42
_42
const { logout } = useAuthentication();
_42
const router = useRouter();
_42
_42
const redoLogin = async (): Promise<void> => {
_42
await logout();
_42
router.replace('/login');
_42
};
_42
_42
const unlock = async (): Promise<void> => {
_42
};
_42
</script>

To redo the login, logout and then navigate to the Login Page.

src/views/UnlockPage.vue

_50
<template>
_50
<ion-page>
_50
<ion-header>
_50
<ion-toolbar>
_50
<ion-title>
_50
Unlock
_50
</ion-title>
_50
</ion-toolbar>
_50
</ion-header>
_50
<ion-content class="ion-padding">
_50
<ion-list>
_50
<ion-item>
_50
<ion-label>
_50
<ion-button expand="block" @click="unlock" data-testid="unlock">Unlock</ion-button>
_50
</ion-label>
_50
</ion-item>
_50
<ion-item>
_50
<ion-label>
_50
<ion-button expand="block" @click="redoLogin" data-testid="redo-login">Redo Login</ion-button>
_50
</ion-label>
_50
</ion-item>
_50
</ion-list>
_50
</ion-content>
_50
</ion-page>
_50
</template>
_50
_50
<script setup lang="ts">
_50
import { useAuthentication } from '@/composables/authentication';
_50
import { useSessionVault } from '@/composables/session-vault';
_50
import { IonButton, IonContent, IonHeader, IonItem, IonLabel, IonList, IonPage, IonTitle, IonToolbar } from '@ionic/vue';
_50
import { useRouter } from 'vue-router';
_50
_50
const { logout } = useAuthentication();
_50
const { unlockSession } = useSessionVault();
_50
const router = useRouter();
_50
_50
const redoLogin = async (): Promise<void> => {
_50
await logout();
_50
router.replace('/login');
_50
};
_50
_50
const unlock = async (): Promise<void> => {
_50
try {
_50
await unlockSession();
_50
router.replace('/tabs/tab1');
_50
} catch (err: unknown) {
_50
null;
_50
}
_50
};
_50
</script>

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.

src/views/UnlockPage.vue

_10
<template>
_10
<ion-page>
_10
<ion-content> </ion-content>
_10
</ion-page>
_10
</template>
_10
_10
<script setup lang="ts">
_10
import { IonContent, IonPage } from '@ionic/vue';
_10
</script>

Handle a Vault Locking In-App

The vault will lock upon resume if the application has been in the background more than lockAfterBackgrounded 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.

src/App.vue

_25
<template>
_25
<ion-app>
_25
<ion-router-outlet />
_25
</ion-app>
_25
</template>
_25
_25
<script setup lang="ts">
_25
import { IonApp, IonRouterOutlet } from '@ionic/vue';
_25
import { watch } from 'vue';
_25
import { useRouter } from 'vue-router';
_25
import { useSessionVault } from '@/composables/session-vault';
_25
_25
const router = useRouter();
_25
const { session, sessionIsLocked, unlockSession } = useSessionVault();
_25
_25
watch(session, async () => {
_25
if (await sessionIsLocked()) {
_25
try {
_25
await unlockSession();
_25
} catch (err: unknown) {
_25
router.replace('/unlock');
_25
}
_25
}
_25
});
_25
</script>

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.

src/views/Tab1Page.vue

_79
<template>
_79
<ion-page>
_79
<ion-header>
_79
<ion-toolbar>
_79
<ion-title>Tab 1</ion-title>
_79
</ion-toolbar>
_79
</ion-header>
_79
<ion-content :fullscreen="true">
_79
<ion-header collapse="condense">
_79
<ion-toolbar>
_79
<ion-title size="large">Tab 1</ion-title>
_79
</ion-toolbar>
_79
</ion-header>
_79
_79
<ion-list>
_79
<ion-item>
_79
<ion-label>
_79
<ion-button expand="block" color="danger" @click="logoutClicked">Logout</ion-button>
_79
</ion-label>
_79
</ion-item>
_79
<ion-item>
_79
<ion-label>
_79
<ion-button expand="block" color="secondary" @click="updateUnlockMode('BiometricsWithPasscode')"
_79
>Use Biometrics</ion-button
_79
>
_79
</ion-label>
_79
</ion-item>
_79
<ion-item>
_79
<ion-label>
_79
<ion-button expand="block" color="secondary" @click="updateUnlockMode('InMemory')"
_79
>Use In Memory</ion-button
_79
>
_79
</ion-label>
_79
</ion-item>
_79
<ion-item>
_79
<ion-label>
_79
<ion-button expand="block" color="secondary" @click="updateUnlockMode('SecureStorage')"
_79
>Use Secure Storage</ion-button
_79
>
_79
</ion-label>
_79
</ion-item>
_79
<ion-item>
_79
<div>
_79
<div>{{ session?.email }}</div>
_79
<div>{{ session?.firstName }} {{ session?.lastName }}</div>
_79
<div>{{ session?.accessToken }}</div>
_79
<div>{{ session?.refreshToken }}</div>
_79
</div>
_79
</ion-item>
_79
</ion-list>
_79
</ion-content>
_79
</ion-page>
_79
</template>
_79
_79
<script setup lang="ts">
_79
import { useAuthentication } from '@/composables/authentication';
_79
import { useSessionVault } from '@/composables/session-vault';
_79
import {
_79
IonButton,
_79
IonContent,
_79
IonHeader,
_79
IonItem,
_79
IonLabel,
_79
IonList,
_79
IonPage,
_79
IonTitle,
_79
IonToolbar,
_79
} from '@ionic/vue';
_79
import { useRouter } from 'vue-router';
_79
_79
const { logout } = useAuthentication();
_79
const { session, updateUnlockMode } = useSessionVault();
_79
const router = useRouter();
_79
_79
const logoutClicked = async (): Promise<void> => {
_79
await logout();
_79
router.replace('/');
_79
};
_79
</script>

Next Steps

In this tutorial, we created a good basic application startup workflow. This is an example of a good workflow, but it is not the only potential flow. For example, our application simply navigates to /tabs/tab1 after unlocking the vault. You could, however, store information about the current state of the application and then restore to that state after unlocking the application. Do whatever is right for your application.