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 application without a startup or login process.

The Login Page

Our application currently just starts 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, then you are good to go. If you need the code you can make a copy from our GitHub repository.

Generate New Pages

In order to implement our startup and authentication strategies, we need to create some new pages:

  • LoginPage: Handle the our simple login flow.
  • StartPage: The "default" route page to use on startup. This page will determine if the user needs to login, try to unlock a vault, go directly into the application.
  • UnlockPage: Provide the user with options if they cancel an unlock attempt.

Generate these pages.

terminal

_10
ionic generate page login
_10
ionic generate page start
_10
ionic generate page unlock

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/app/app.routes.ts file to:

  • Define the /tabs route.
  • Define the /login route.
  • Define the /start route.
  • Define the /unlock route.
  • Create a redirect from / to /start.
src/app/app.routes.ts

_25
import { Routes } from '@angular/router';
_25
_25
export const routes: Routes = [
_25
{
_25
path: '',
_25
redirectTo: '/start',
_25
pathMatch: 'full',
_25
},
_25
{
_25
path: 'tabs',
_25
loadChildren: () => import('./tabs/tabs.routes').then((m) => m.routes),
_25
},
_25
{
_25
path: 'login',
_25
loadComponent: () => import('./login/login.page').then((m) => m.LoginPage),
_25
},
_25
{
_25
path: 'start',
_25
loadComponent: () => import('./start/start.page').then((m) => m.StartPage),
_25
},
_25
{
_25
path: 'unlock',
_25
loadComponent: () => import('./unlock/unlock.page').then((m) => m.UnlockPage),
_25
},
_25
];

The TabsPage (route: /tabs) has sub-pages. The sub-pages are already set up, but we need to make the following adjustments to src/app/tabs/tabs.routes.ts:

  • Remove the redirect for / since it was moved to src/app/app.routes.ts.
  • Change the main path from tabs (which is now defined in src/app/app.routes.ts) to an empty string.
src/app/tabs/tabs.routes.ts

_28
import { Routes } from '@angular/router';
_28
import { TabsPage } from './tabs.page';
_28
_28
export const routes: Routes = [
_28
{
_28
path: '',
_28
component: TabsPage,
_28
children: [
_28
{
_28
path: 'tab1',
_28
loadComponent: () => import('../tab1/tab1.page').then((m) => m.Tab1Page),
_28
},
_28
{
_28
path: 'tab2',
_28
loadComponent: () => import('../tab2/tab2.page').then((m) => m.Tab2Page),
_28
},
_28
{
_28
path: 'tab3',
_28
loadComponent: () => import('../tab3/tab3.page').then((m) => m.Tab3Page),
_28
},
_28
{
_28
path: '',
_28
redirectTo: '/tabs/tab1',
_28
pathMatch: 'full',
_28
},
_28
],
_28
},
_28
];

The AuthenticationService

Part of our startup strategy involves authentication. We will not really be performing authentication, but we will add the service so that we have the infrastructure in place so we can later add authentication via a solution such as Auth Connect.

Terminal

_10
ionic generate service core/authentication

Generate the authentication service.

Terminal
src/app/core/authentication.service.ts

_10
import { Injectable } from '@angular/core';
_10
_10
@Injectable({
_10
providedIn: 'root',
_10
})
_10
export class AuthenticationService {
_10
constructor() {}
_10
}

An empty service is created.

Terminal
src/app/core/authentication.service.ts

_10
import { Injectable } from '@angular/core';
_10
import { SessionVaultService } from './session-vault.service';
_10
_10
@Injectable({
_10
providedIn: 'root',
_10
})
_10
export class AuthenticationService {
_10
constructor(private sessionVault: SessionVaultService) {}
_10
}

Inject the SessionVaultService.

Terminal
src/app/core/authentication.service.ts

_19
import { Injectable } from '@angular/core';
_19
import { SessionVaultService } from './session-vault.service';
_19
_19
@Injectable({
_19
providedIn: 'root',
_19
})
_19
export class AuthenticationService {
_19
constructor(private sessionVault: SessionVaultService) {}
_19
_19
async login(): Promise<void> {
_19
this.sessionVault.storeSession({
_19
email: 'test@ionic.io',
_19
firstName: 'Tessa',
_19
lastName: 'Testsmith',
_19
accessToken: '4abf1d79-143c-4b89-b478-19607eb5ce97',
_19
refreshToken: '565111b6-66c3-4527-9238-6ea2cc017126',
_19
});
_19
}
_19
}

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

Terminal
src/app/core/authentication.service.ts

_23
import { Injectable } from '@angular/core';
_23
import { SessionVaultService } from './session-vault.service';
_23
_23
@Injectable({
_23
providedIn: 'root',
_23
})
_23
export class AuthenticationService {
_23
constructor(private sessionVault: SessionVaultService) {}
_23
_23
async login(): Promise<void> {
_23
this.sessionVault.storeSession({
_23
email: 'test@ionic.io',
_23
firstName: 'Tessa',
_23
lastName: 'Testsmith',
_23
accessToken: '4abf1d79-143c-4b89-b478-19607eb5ce97',
_23
refreshToken: '565111b6-66c3-4527-9238-6ea2cc017126',
_23
});
_23
}
_23
_23
async logout(): Promise<void> {
_23
this.sessionVault.clearSession();
_23
}
_23
}

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

Terminal
src/app/core/authentication.service.ts

_28
import { Injectable } from '@angular/core';
_28
import { SessionVaultService } from './session-vault.service';
_28
_28
@Injectable({
_28
providedIn: 'root',
_28
})
_28
export class AuthenticationService {
_28
constructor(private sessionVault: SessionVaultService) {}
_28
_28
async login(): Promise<void> {
_28
this.sessionVault.storeSession({
_28
email: 'test@ionic.io',
_28
firstName: 'Tessa',
_28
lastName: 'Testsmith',
_28
accessToken: '4abf1d79-143c-4b89-b478-19607eb5ce97',
_28
refreshToken: '565111b6-66c3-4527-9238-6ea2cc017126',
_28
});
_28
}
_28
_28
async logout(): Promise<void> {
_28
this.sessionVault.clearSession();
_28
}
_28
_28
async isAuthenticated(): Promise<boolean> {
_28
const session = await this.sessionVault.getSession();
_28
return !!session;
_28
}
_28
}

To determine if the user is authenticated, check for a stored session.

Generate the authentication service.

An empty service is created.

Inject the SessionVaultService.

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.

Terminal

_10
ionic generate service core/authentication

We now have an AuthenticationService that we can use in the rest of our app. We also have a service that we can update to add our actual authentication services using a solution such as Auth Connect.

The LoginPage

The login page simply includes a "Login" button.

src/app/login/login.page.html

_21
<ion-header [translucent]="true">
_21
<ion-toolbar>
_21
<ion-title>Login</ion-title>
_21
</ion-toolbar>
_21
</ion-header>
_21
_21
<ion-content [fullscreen]="true">
_21
<ion-header collapse="condense">
_21
<ion-toolbar>
_21
<ion-title size="large">Login</ion-title>
_21
</ion-toolbar>
_21
</ion-header>
_21
_21
<ion-list>
_21
<ion-item>
_21
<ion-label>
_21
<ion-button expand="block" (click)="login()">Login</ion-button>
_21
</ion-label>
_21
</ion-item>
_21
</ion-list>
_21
</ion-content>

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/app/login/login.page.ts

_36
import { Component } from '@angular/core';
_36
import {
_36
IonButton,
_36
IonContent,
_36
IonHeader,
_36
IonItem,
_36
IonLabel,
_36
IonList,
_36
IonTitle,
_36
IonToolbar,
_36
NavController,
_36
} from '@ionic/angular/standalone';
_36
import { AuthenticationService } from '../core/authentication.service';
_36
_36
@Component({
_36
selector: 'app-login',
_36
templateUrl: './login.page.html',
_36
styleUrls: ['./login.page.scss'],
_36
standalone: true,
_36
imports: [IonButton, IonContent, IonHeader, IonItem, IonLabel, IonList, IonTitle, IonToolbar],
_36
})
_36
export class LoginPage {
_36
constructor(
_36
private navController: NavController,
_36
private authentication: AuthenticationService,
_36
) {}
_36
_36
async login() {
_36
try {
_36
await this.authentication.login();
_36
this.navController.navigateRoot(['tabs', 'tab1']);
_36
} catch (err: unknown) {
_36
console.error('Failed to log in', err);
_36
}
_36
}
_36
}

Update the SessionVaultService

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 SessionVaultService to provide unlock() and isLocked() methods.

src/app/code/session-vault.service.ts

_94
import { Injectable } from '@angular/core';
_94
import {
_94
BrowserVault,
_94
DeviceSecurityType,
_94
IdentityVaultConfig,
_94
Vault,
_94
VaultType,
_94
} from '@ionic-enterprise/identity-vault';
_94
import { Session } from '../models/session';
_94
import { VaultFactory } from './vault.factory';
_94
import { Observable, Subject } from 'rxjs';
_94
_94
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_94
_94
@Injectable({
_94
providedIn: 'root',
_94
})
_94
export class SessionVaultService {
_94
private lockedSubject: Subject<boolean>;
_94
this.vault.config?.type !== VaultType.SecureStorage &&

In the isLocked() method, we are ignoring the actual state for SecureStorage or InMemory type vaults 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/app/start/start.page.html

_10
<ion-content> </ion-content>

We can also trim the component code down to just a minimal Component class which will make it easier to build it back up again.

src/app/start/start.page.ts

_15
import { Component, OnInit } from '@angular/core';
_15
import { IonContent } from '@ionic/angular/standalone';
_15
_15
@Component({
_15
selector: 'app-start',
_15
templateUrl: './start.page.html',
_15
styleUrls: ['./start.page.scss'],
_15
standalone: true,
_15
imports: [IonContent],
_15
})
_15
export class StartPage implements OnInit {
_15
constructor() {}
_15
_15
async ngOnInit(): Promise<void> {}
_15
}

With the basics in place, let's implement the rest of the logic.

src/app/start/start.page.ts

_22
import { Component, OnInit } from '@angular/core';
_22
import { IonContent } from '@ionic/angular/standalone';
_22
_22
@Component({
_22
selector: 'app-start',
_22
templateUrl: './start.page.html',
_22
styleUrls: ['./start.page.scss'],
_22
standalone: true,
_22
imports: [IonContent],
_22
})
_22
export class StartPage implements OnInit {
_22
constructor() {}
_22
_22
async ngOnInit() {
_22
await this.performUnlock();
_22
await this.performNavigation();
_22
}
_22
_22
private async performNavigation(): Promise<void> {}
_22
_22
private async performUnlock(): Promise<void> {}
_22
}

As noted above, the page may attempt an unlock operation, and it will navigate somewhere based on the current authentication state.

src/app/start/start.page.ts

_32
import { Component, OnInit } from '@angular/core';
_32
import { IonContent } from '@ionic/angular/standalone';
_32
import { SessionVaultService } from '../core/session-vault.service';
_32
_32
@Component({
_32
selector: 'app-start',
_32
templateUrl: './start.page.html',
_32
styleUrls: ['./start.page.scss'],
_32
standalone: true,
_32
imports: [IonContent],
_32
})
_32
export class StartPage implements OnInit {
_32
constructor(
_32
private sessionVault: SessionVaultService,
_32
) {}
_32
_32
async ngOnInit() {
_32
await this.performUnlock();
_32
await this.performNavigation();
_32
}
_32
_32
private async performNavigation(): Promise<void> {}
_32
_32
private async performUnlock(): Promise<void> {
_32
if (await this.sessionVault.isLocked()) {
_32
try {
_32
await this.sessionVault.unlock();
_32
} catch (err: unknown) {
_32
this.navController.navigateRoot(['unlock']);
_32
}
_32
}
_32
}

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/app/start/start.page.ts

_43
import { Component, OnInit } from '@angular/core';
_43
import { IonContent, NavController } from '@ionic/angular/standalone';
_43
import { AuthenticationService } from '../core/authentication.service';
_43
import { SessionVaultService } from '../core/session-vault.service';
_43
_43
@Component({
_43
selector: 'app-start',
_43
templateUrl: './start.page.html',
_43
styleUrls: ['./start.page.scss'],
_43
standalone: true,
_43
imports: [IonContent],
_43
})
_43
export class StartPage implements OnInit {
_43
constructor(
_43
private authentication: AuthenticationService,
_43
private navController: NavController,
_43
private sessionVault: SessionVaultService,
_43
) {}
_43
_43
async ngOnInit() {
_43
await this.performUnlock();
_43
await this.performNavigation();
_43
}
_43
_43
private async performNavigation(): Promise<void> {
_43
if (!(await this.sessionVault.isLocked())) {
_43
if (await this.authentication.isAuthenticated()) {
_43
this.navController.navigateRoot(['tabs', 'tab1']);
_43
} else {
_43
this.navController.navigateRoot(['login']);
_43
}
_43
}
_43
}
_43
_43
private async performUnlock(): Promise<void> {
_43
if (await this.sessionVault.isLocked()) {
_43
try {
_43
await this.sessionVault.unlock();
_43
} catch (err: unknown) {
_43
this.navController.navigateRoot(['unlock']);
_43
}
_43
}
_43
}

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.

As noted above, 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/app/start/start.page.ts

_22
import { Component, OnInit } from '@angular/core';
_22
import { IonContent } from '@ionic/angular/standalone';
_22
_22
@Component({
_22
selector: 'app-start',
_22
templateUrl: './start.page.html',
_22
styleUrls: ['./start.page.scss'],
_22
standalone: true,
_22
imports: [IonContent],
_22
})
_22
export class StartPage implements OnInit {
_22
constructor() {}
_22
_22
async ngOnInit() {
_22
await this.performUnlock();
_22
await this.performNavigation();
_22
}
_22
_22
private async performNavigation(): Promise<void> {}
_22
_22
private async performUnlock(): Promise<void> {}
_22
}

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/app/unlock/unlock.page.html
src/app/unlock/unlock.page.ts

_13
<ion-header [translucent]="true">
_13
<ion-toolbar>
_13
<ion-title>unlock</ion-title>
_13
</ion-toolbar>
_13
</ion-header>
_13
_13
<ion-content [fullscreen]="true">
_13
<ion-header collapse="condense">
_13
<ion-toolbar>
_13
<ion-title size="large">unlock</ion-title>
_13
</ion-toolbar>
_13
</ion-header>
_13
</ion-content>

We start with the template page code

src/app/unlock/unlock.page.html
src/app/unlock/unlock.page.ts

_26
<ion-header [translucent]="true">
_26
<ion-toolbar>
_26
<ion-title>unlock</ion-title>
_26
</ion-toolbar>
_26
</ion-header>
_26
_26
<ion-content [fullscreen]="true">
_26
<ion-header collapse="condense">
_26
<ion-toolbar>
_26
<ion-title size="large">unlock</ion-title>
_26
</ion-toolbar>
_26
</ion-header>
_26
_26
<ion-list>
_26
<ion-item>
_26
<ion-label>
_26
<ion-button expand="block" (click)="unlock()">Unlock</ion-button>
_26
</ion-label>
_26
</ion-item>
_26
<ion-item>
_26
<ion-label>
_26
<ion-button expand="block" (click)="redoLogin()">Redo Login</ion-button>
_26
</ion-label>
_26
</ion-item>
_26
</ion-list>
_26
</ion-content>

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

src/app/unlock/unlock.page.html
src/app/unlock/unlock.page.ts

_45
import { Component, OnInit } from '@angular/core';
_45
import { CommonModule } from '@angular/common';
_45
import { FormsModule } from '@angular/forms';
_45
import {
_45
IonButton,
_45
IonContent,
_45
IonHeader,
_45
IonItem,
_45
IonLabel,
_45
IonList,
_45
IonTitle,
_45
IonToolbar,
_45
} from '@ionic/angular/standalone';
_45
_45
@Component({
_45
selector: 'app-unlock',
_45
templateUrl: './unlock.page.html',
_45
styleUrls: ['./unlock.page.scss'],
_45
standalone: true,
_45
imports: [
_45
IonButton,
_45
IonContent,
_45
IonHeader,
_45
IonItem,
_45
IonLabel,
_45
IonList,
_45
IonTitle,
_45
IonToolbar,
_45
CommonModule,
_45
FormsModule,
_45
],
_45
})
_45
export class UnlockPage implements OnInit {
_45
_45
constructor() { }
_45
_45
ngOnInit() {
_45
}
_45
_45
async redoLogin(): Promise<void> {
_45
}
_45
_45
async unlock(): Promise<void> {
_45
}
_45
}

Add the components we are using as well as shells for the click event handlers.

src/app/unlock/unlock.page.html
src/app/unlock/unlock.page.ts

_52
import { Component, OnInit } from '@angular/core';
_52
import { CommonModule } from '@angular/common';
_52
import { FormsModule } from '@angular/forms';
_52
import {
_52
IonButton,
_52
IonContent,
_52
IonHeader,
_52
IonItem,
_52
IonLabel,
_52
IonList,
_52
IonTitle,
_52
IonToolbar,
_52
NavController,
_52
} from '@ionic/angular/standalone';
_52
import { AuthenticationService } from '../core/authentication.service';
_52
_52
@Component({
_52
selector: 'app-unlock',
_52
templateUrl: './unlock.page.html',
_52
styleUrls: ['./unlock.page.scss'],
_52
standalone: true,
_52
imports: [
_52
IonButton,
_52
IonContent,
_52
IonHeader,
_52
IonItem,
_52
IonLabel,
_52
IonList,
_52
IonTitle,
_52
IonToolbar,
_52
CommonModule,
_52
FormsModule,
_52
],
_52
})
_52
export class UnlockPage implements OnInit {
_52
_52
constructor(
_52
private authentication: AuthenticationService,
_52
private navController: NavController,
_52
) { }
_52
_52
ngOnInit() {
_52
}
_52
_52
async redoLogin(): Promise<void> {
_52
await this.authentication.logout();
_52
this.navController.navigateRoot(['login']);
_52
}
_52
_52
async unlock(): Promise<void> {
_52
}
_52
}

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

src/app/unlock/unlock.page.html
src/app/unlock/unlock.page.ts

_60
import { Component, OnInit } from '@angular/core';
_60
import { CommonModule } from '@angular/common';
_60
import { FormsModule } from '@angular/forms';
_60
import {
_60
IonButton,
_60
IonContent,
_60
IonHeader,
_60
IonItem,
_60
IonLabel,
_60
IonList,
_60
IonTitle,
_60
IonToolbar,
_60
NavController,
_60
} from '@ionic/angular/standalone';
_60
import { AuthenticationService } from '../core/authentication.service';
_60
import { SessionVaultService } from '../core/session-vault.service';
_60
_60
@Component({
_60
selector: 'app-unlock',
_60
templateUrl: './unlock.page.html',
_60
styleUrls: ['./unlock.page.scss'],
_60
standalone: true,
_60
imports: [
_60
IonButton,
_60
IonContent,
_60
IonHeader,
_60
IonItem,
_60
IonLabel,
_60
IonList,
_60
IonTitle,
_60
IonToolbar,
_60
CommonModule,
_60
FormsModule,
_60
],
_60
})
_60
export class UnlockPage implements OnInit {
_60
_60
constructor(
_60
private authentication: AuthenticationService,
_60
private navController: NavController,
_60
private sessionVault: SessionVaultService,
_60
) { }
_60
_60
ngOnInit() {
_60
}
_60
_60
async redoLogin(): Promise<void> {
_60
await this.authentication.logout();
_60
this.navController.navigateRoot(['login']);
_60
}
_60
_60
async unlock(): Promise<void> {
_60
try {
_60
await this.sessionVault.unlock();
_60
this.navController.navigateRoot(['tabs', 'tab1']);
_60
} catch (err: unknown) {
_60
null;
_60
}
_60
}
_60
}

Try to unlock the vault and navigate to the first page of the app if successful.

We start with the template page code

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

Add the components we are using as well as shells for the click event handlers.

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/app/unlock/unlock.page.html
src/app/unlock/unlock.page.ts

_13
<ion-header [translucent]="true">
_13
<ion-toolbar>
_13
<ion-title>unlock</ion-title>
_13
</ion-toolbar>
_13
</ion-header>
_13
_13
<ion-content [fullscreen]="true">
_13
<ion-header collapse="condense">
_13
<ion-toolbar>
_13
<ion-title size="large">unlock</ion-title>
_13
</ion-toolbar>
_13
</ion-header>
_13
</ion-content>

At this point the UnlockPage is functionally complete, but the OnInit logic is not being used and should be removed. Doing so is left as an exercise for the reader.

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 AppComponent is a good centralized location from which to handle this logic.

src/app/app.component.ts

_30
import { Component, OnDestroy } from '@angular/core';
_30
import { IonApp, IonRouterOutlet, NavController } from '@ionic/angular/standalone';
_30
import { Subscription } from 'rxjs';
_30
import { SessionVaultService } from './core/session-vault.service';
_30
_30
@Component({
_30
selector: 'app-root',
_30
templateUrl: 'app.component.html',
_30
standalone: true,
_30
imports: [IonApp, IonRouterOutlet],
_30
})
_30
export class AppComponent implements OnDestroy {
_30
private subscription: Subscription;
_30
_30
constructor(navController: NavController, sessionVault: SessionVaultService) {
_30
this.subscription = sessionVault.locked$.subscribe(async (lock) => {
_30
if (lock) {
_30
try {
_30
await sessionVault.unlock();
_30
} catch (err: unknown) {
_30
navController.navigateRoot(['unlock']);
_30
}
_30
}
_30
});
_30
}
_30
_30
ngOnDestroy() {
_30
this.subscription.unsubscribe();
_30
}
_30
}

Cleanup the Tab1Page

This step is completely optional. The tutorial application will work perfectly fine with the code as-is. 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 can 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 "Unlock" button and all code associated with it.
  • Remove the code that subscribes to the locked$ observable and clears the displayed session data when the vault is locked. This is no longer needed because we navigate away from the page entirely when the vault locks.

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/app/tab1/tab1.page.html
src/app/tab1/tab1.page.ts

_53
<ion-header [translucent]="true">
_53
<ion-toolbar>
_53
<ion-title> Tab1 </ion-title>
_53
</ion-toolbar>
_53
</ion-header>
_53
_53
<ion-content [fullscreen]="true">
_53
<ion-header collapse="condense">
_53
<ion-toolbar>
_53
<ion-title size="large">Tab1</ion-title>
_53
</ion-toolbar>
_53
</ion-header>
_53
_53
<ion-list>
_53
<ion-item>
_53
<ion-label>
_53
<ion-button expand="block" color="danger" (click)="logout()">Logout</ion-button>
_53
</ion-label>
_53
</ion-item>
_53
<ion-item>
_53
<ion-label>
_53
<ion-button expand="block" color="secondary" (click)="changeUnlockMode('BiometricsWithPasscode')"
_53
>Use Biometrics</ion-button
_53
>
_53
</ion-label>
_53
</ion-item>
_53
<ion-item>
_53
<ion-label>
_53
<ion-button expand="block" color="secondary" (click)="changeUnlockMode('InMemory')">Use In Memory</ion-button>
_53
</ion-label>
_53
</ion-item>
_53
<ion-item>
_53
<ion-label>
_53
<ion-button expand="block" color="secondary" (click)="changeUnlockMode('SecureStorage')"
_53
>Use Secure Storage</ion-button
_53
>
_53
</ion-label>
_53
</ion-item>
_53
<ion-item>
_53
<ion-label>
_53
<ion-button expand="block" color="warning" (click)="lock()">Lock</ion-button>
_53
</ion-label>
_53
</ion-item>
_53
<ion-item>
_53
<div>
_53
<div>{{session?.email}}</div>
_53
<div>{{session?.firstName}} {{session?.lastName}}</div>
_53
<div>{{session?.accessToken}}</div>
_53
<div>{{session?.refreshToken}}</div>
_53
</div>
_53
</ion-item>
_53
</ion-list>
_53
</ion-content>

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.