Skip to main content
Version: 5.0

Getting Started with Identity Vault

Generate the Application

Before we explore the use of Identity Vault, we need to scaffold an application. In this section, we will generate a tabs-based @ionic/angular application, perform some basic configuration, and add the iOS and Android platforms.

If you need to refresh your memory on the overall developer workflow for Capacitor, please do so now. However, here is a synopsis of the commands you will use the most while performing this tutorial:

  • npm start: Start the development server so the application can be run in the browser.
  • npm run build: Build the web portion of the application.
  • npm cap sync: Copy the web app and any new plugins into the native applications.
  • npm cap open android: Open Android Studio in order to build, run, and debug the application on Android.
  • npm cap open ios: Open Xcode in order to build, run, and debug the application on iOS.

Let's get started.

Terminal

_10
ionic start iv-getting-started tabs --type=angular-standalone

Use the Ionic CLI to generate the application.

Terminal

_10
ionic start iv-getting-started tabs --type=angular-standalone
_10
cd iv-getting-started

Change directory into the newly generated project.

Terminal
capacitor.config.ts

_12
import { CapacitorConfig } from '@capacitor/cli';
_12
_12
const config: CapacitorConfig = {
_12
appId: 'io.ionic.gettingstartediv',
_12
appName: 'iv-getting-started',
_12
webDir: 'www',
_12
server: {
_12
androidScheme: 'https',
_12
},
_12
};
_12
_12
export default config;

Change the appId to be something unique. The appId is used as the bundle ID / application ID. Therefore it should be a string that is unique to your organization and application. We will use io.ionic.gettingstartediv for this application.

It is best to do this before adding the iOS and Android platforms to ensure they are setup properly from the start.

Terminal
capacitor.config.ts

_10
ionic start iv-getting-started-ac tabs --type=angular-standalone
_10
cd iv-getting-started
_10
npm run build
_10
ionic cap add android
_10
ionic cap add ios

Build the application and install the platforms.

Terminal
capacitor.config.ts
package.json

_15
{
_15
"name": "iv-getting-started",
_15
"version": "0.0.1",
_15
"author": "Ionic Framework",
_15
"homepage": "https://ionicframework.com/",
_15
"scripts": {
_15
"ng": "ng",
_15
"start": "ng serve",
_15
"build": "ng build && cap sync",
_15
"watch": "ng build --watch --configuration development",
_15
"test": "ng test",
_15
"lint": "ng lint"
_15
},
_15
...
_15
}

We should do a cap sync with each build. Change the scripts in package.json to do this.

Use the Ionic CLI to generate the application.

Change directory into the newly generated project.

Change the appId to be something unique. The appId is used as the bundle ID / application ID. Therefore it should be a string that is unique to your organization and application. We will use io.ionic.gettingstartediv for this application.

It is best to do this before adding the iOS and Android platforms to ensure they are setup properly from the start.

Build the application and install the platforms.

We should do a cap sync with each build. Change the scripts in package.json to do this.

Terminal

_10
ionic start iv-getting-started tabs --type=angular-standalone

Install Identity Vault

In order to install Identity Vault, you will need to use ionic enterprise register register your product key. This will create a .npmrc file containing the product key.

If you have already performed that step for your production application, you can just copy the .npmrc file from your production project. Since this application is for learning purposes only, you don't need to obtain another key.

You can now install Identity Vault and sync the platforms:

Terminal

_10
npm install @ionic-enterprise/identity-vault
_10
npx cap sync

Create the SessionVaultService

Our tutorial application will have a single vault that simulates storing our application's authentication session information. The vault is managed via our SessionVaultService. Generate that now.

Terminal

_10
ionic generate service core/session-vault

Also create a simple "Vault Factory" class.

Terminal

_10
ionic generate class core/Vault --type factory --skip-tests

The purpose of this class is two-fold:

  1. It hides the fact that a different vault is used on native vs. browser platforms.
  2. It facilitates mocking the vault, which makes unit testing easier.
src/app/core/vault.factory.ts

_10
import { Capacitor } from '@capacitor/core';
_10
import { BrowserVault, Vault } from '@ionic-enterprise/identity-vault';
_10
_10
export class VaultFactory {
_10
static create(): BrowserVault | Vault {
_10
return Capacitor.isNativePlatform() ? new Vault() : new BrowserVault();
_10
}
_10
}

Create and Initialize the Vault

Before we use Identity Vault, we need to make sure that our vault is properly created and initialized. It is important to note that creation and initialization are different processes. Creation is performed when the service is constructed and is limited to the creation of a JavaScript object.

The initialization involves communication with the native layer. As such it is asynchronous. Since initialization needs to complete before we can begin normal operation of the application, we run the initialization using the APP_INITIALIZER and await its completion.

important

Awaiting the completion of initialization in this manner is a best-practice that should always be followed.

src/app/core/session-vault.service.ts
src/main.ts

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

We will build this service up to perform the vault creation and initialization.

src/app/core/session-vault.service.ts
src/main.ts

_14
import { Injectable } from '@angular/core';
_14
import { BrowserVault, Vault } from '@ionic-enterprise/identity-vault';
_14
import { VaultFactory } from './vault.factory';
_14
_14
@Injectable({
_14
providedIn: 'root',
_14
})
_14
export class SessionVaultService {
_14
private vault: BrowserVault | Vault;
_14
_14
constructor() {
_14
this.vault = VaultFactory.create();
_14
}
_14
}

Create the vault using our factory class.

src/app/core/session-vault.service.ts
src/main.ts

_26
import { Injectable } from '@angular/core';
_26
import { BrowserVault, DeviceSecurityType, Vault, VaultType } from '@ionic-enterprise/identity-vault';
_26
import { VaultFactory } from './vault.factory';
_26
_26
@Injectable({
_26
providedIn: 'root',
_26
})
_26
export class SessionVaultService {
_26
private vault: BrowserVault | Vault;
_26
_26
constructor() {
_26
this.vault = VaultFactory.create();
_26
}
_26
_26
async initialize(): Promise<void> {
_26
try {
_26
await this.vault.initialize({
_26
key: 'io.ionic.gettingstartediv',
_26
type: VaultType.SecureStorage,
_26
deviceSecurityType: DeviceSecurityType.None,
_26
});
_26
} catch (e: unknown) {
_26
await this.vault.clear();
_26
}
_26
}
_26
}

Create an initialize() method from which we will perform all vault initialization. At this time, the only thing we need to do is pass a configuration object to our vault. The meaning of the configuration properties will be explained later.

If the initialize() fails the best thing to do with the vault is to clear it.

src/app/core/session-vault.service.ts
src/main.ts

_33
import { APP_INITIALIZER, enableProdMode } from '@angular/core';
_33
import { bootstrapApplication } from '@angular/platform-browser';
_33
import { RouteReuseStrategy, provideRouter } from '@angular/router';
_33
import { IonicRouteStrategy, provideIonicAngular } from '@ionic/angular/standalone';
_33
_33
import { routes } from './app/app.routes';
_33
import { AppComponent } from './app/app.component';
_33
import { environment } from './environments/environment';
_33
import { SessionVaultService } from './app/core/session-vault.service';
_33
_33
if (environment.production) {
_33
enableProdMode();
_33
}
_33
_33
const appInitFactory =
_33
(vault: SessionVaultService): (() => Promise<void>) =>
_33
async () => {
_33
await vault.initialize();
_33
};
_33
_33
bootstrapApplication(AppComponent, {
_33
providers: [
_33
{
_33
provide: APP_INITIALIZER,
_33
useFactory: appInitFactory,
_33
deps: [SessionVaultService],
_33
multi: true,
_33
},
_33
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
_33
provideIonicAngular(),
_33
provideRouter(routes),
_33
],
_33
});

In src/main.ts use an APP_INITIALIZER to make sure our vault is fully initialized on startup before the AppComponent is mounted.

We will build this service up to perform the vault creation and initialization.

Create the vault using our factory class.

Create an initialize() method from which we will perform all vault initialization. At this time, the only thing we need to do is pass a configuration object to our vault. The meaning of the configuration properties will be explained later.

If the initialize() fails the best thing to do with the vault is to clear it.

In src/main.ts use an APP_INITIALIZER to make sure our vault is fully initialized on startup before the AppComponent is mounted.

src/app/core/session-vault.service.ts
src/main.ts

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

In this section, we created a vault using the key io.ionic.gettingstartediv. Our vault is a "Secure Storage" vault, which means that the information we store in the vault is encrypted in the keychain / keystore and is only visible to our application, but the vault is never locked. We will explore other types of vaults later in this tutorial.

Store a Value

Let's store some data in the vault. Here, we will:

  • Define our session information.
  • Add a method to SessionVaultService to store a session.
  • Add a button to Tab1Page to store a fake session.

First, let's define the shape of our authentication session data via:

Terminal

_10
ionic generate interface models/Session

src/app/models/session.ts

_10
export interface Session {
_10
firstName: string;
_10
lastName: string;
_10
email: string;
_10
accessToken: string;
_10
refreshToken: string;
_10
}

We can store multiple items within the vault, each with their own key. For this application, we will store a single item with the key of session. The vault has a setValue() method that is used for this purpose. Modify src/app/core/session-vault.service.ts to store the session.

src/app/core/session-value.service.ts

_31
import { Injectable } from '@angular/core';
_31
import { BrowserVault, DeviceSecurityType, Vault, VaultType } from '@ionic-enterprise/identity-vault';
_31
import { VaultFactory } from './vault.factory';
_31
import { Session } from '../models/session';
_31
_31
@Injectable({
_31
providedIn: 'root',
_31
})
_31
export class SessionVaultService {
_31
private vault: BrowserVault | Vault;
_31
_31
constructor() {
_31
this.vault = VaultFactory.create();
_31
}
_31
_31
async initialize(): Promise<void> {
_31
try {
_31
await this.vault.initialize({
_31
key: 'io.ionic.gettingstartediv',
_31
type: VaultType.SecureStorage,
_31
deviceSecurityType: DeviceSecurityType.None,
_31
});
_31
} catch (e: unknown) {
_31
await this.vault.clear();
_31
}
_31
}
_31
_31
async storeSession(session: Session): Promise<void> {
_31
this.vault.setValue('session', session);
_31
}
_31
}

Notice that we have created a very light wrapper around the vault's setValue() method. This is often all that is required. You may be tempted to just make the SessionVaultService's vault property public and then directly use the Identity Vault methods directly on the vault. It is best-practice, however, to encapsulate the vault in a service like this one and only expose the functionality that makes sense for your application.

With the "store session" feature properly abstracted, add method properly dd a button to the Tab1Page that will simulate logging in by storing some fake authentication data in the vault.

src/app/tab1/tab1.page.html
src/app/tab1/tab1.page.ts

_15
<ion-header [translucent]="true">
_15
<ion-toolbar>
_15
<ion-title> Tab 1 </ion-title>
_15
</ion-toolbar>
_15
</ion-header>
_15
_15
<ion-content [fullscreen]="true">
_15
<ion-header collapse="condense">
_15
<ion-toolbar>
_15
<ion-title size="large">Tab 1</ion-title>
_15
</ion-toolbar>
_15
</ion-header>
_15
_15
<app-explore-container name="Tab 1 page"></app-explore-container>
_15
</ion-content>

We are currently displaying the generic starter "Explore Container" data.

src/app/tab1/tab1.page.html
src/app/tab1/tab1.page.ts

_21
<ion-header [translucent]="true">
_21
<ion-toolbar>
_21
<ion-title> Tab 1 </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">Tab 1</ion-title>
_21
</ion-toolbar>
_21
</ion-header>
_21
_21
<ion-list>
_21
<ion-item>
_21
<ion-label>
_21
<ion-button expand="block" (click)="storeSession()">Store</ion-button>
_21
</ion-label>
_21
</ion-item>
_21
</ion-list>
_21
</ion-content>

Replace the explore container with a list containing a button.

src/app/tab1/tab1.page.html
src/app/tab1/tab1.page.ts

_22
import { Component } from '@angular/core';
_22
import {
_22
IonButton,
_22
IonContent,
_22
IonHeader,
_22
IonItem,
_22
IonLabel,
_22
IonList,
_22
IonTitle,
_22
IonToolbar,
_22
} from '@ionic/angular/standalone';
_22
_22
@Component({
_22
selector: 'app-tab1',
_22
templateUrl: 'tab1.page.html',
_22
styleUrls: ['tab1.page.scss'],
_22
standalone: true,
_22
imports: [IonButton, IonContent, IonHeader, IonItem, IonLabel, IonList, IonTitle, IonToolbar],
_22
})
_22
export class Tab1Page {
_22
constructor() {}
_22
}

Import the Ionic components that we added to the page template.

src/app/tab1/tab1.page.html
src/app/tab1/tab1.page.ts

_23
import { Component } from '@angular/core';
_23
import {
_23
IonButton,
_23
IonContent,
_23
IonHeader,
_23
IonItem,
_23
IonLabel,
_23
IonList,
_23
IonTitle,
_23
IonToolbar,
_23
} from '@ionic/angular/standalone';
_23
import { SessionVaultService } from '../core/session-vault.service';
_23
_23
@Component({
_23
selector: 'app-tab1',
_23
templateUrl: 'tab1.page.html',
_23
styleUrls: ['tab1.page.scss'],
_23
standalone: true,
_23
imports: [IonButton, IonContent, IonHeader, IonItem, IonLabel, IonList, IonTitle, IonToolbar],
_23
})
_23
export class Tab1Page {
_23
constructor(private sessionVault: SessionVaultService) {}
_23
}

Inject the vault service.

src/app/tab1/tab1.page.html
src/app/tab1/tab1.page.ts

_33
import { Component } from '@angular/core';
_33
import {
_33
IonButton,
_33
IonContent,
_33
IonHeader,
_33
IonItem,
_33
IonLabel,
_33
IonList,
_33
IonTitle,
_33
IonToolbar,
_33
} from '@ionic/angular/standalone';
_33
import { SessionVaultService } from '../core/session-vault.service';
_33
_33
@Component({
_33
selector: 'app-tab1',
_33
templateUrl: 'tab1.page.html',
_33
styleUrls: ['tab1.page.scss'],
_33
standalone: true,
_33
imports: [IonButton, IonContent, IonHeader, IonItem, IonLabel, IonList, IonTitle, IonToolbar],
_33
})
_33
export class Tab1Page {
_33
constructor(private sessionVault: SessionVaultService) {}
_33
_33
async storeSession(): Promise<void> {
_33
await this.sessionVault.storeSession({
_33
email: 'test@ionic.io',
_33
firstName: 'Tessa',
_33
lastName: 'Testsmith',
_33
accessToken: '4abf1d79-143c-4b89-b478-19607eb5ce97',
_33
refreshToken: '565111b6-66c3-4527-9238-6ea2cc017126',
_33
});
_33
}
_33
}

Store some made up test data.

We are currently displaying the generic starter "Explore Container" data.

Replace the explore container with a list containing a button.

Import the Ionic components that we added to the page template.

Inject the vault service.

Store some made up test data.

src/app/tab1/tab1.page.html
src/app/tab1/tab1.page.ts

_15
<ion-header [translucent]="true">
_15
<ion-toolbar>
_15
<ion-title> Tab 1 </ion-title>
_15
</ion-toolbar>
_15
</ion-header>
_15
_15
<ion-content [fullscreen]="true">
_15
<ion-header collapse="condense">
_15
<ion-toolbar>
_15
<ion-title size="large">Tab 1</ion-title>
_15
</ion-toolbar>
_15
</ion-header>
_15
_15
<app-explore-container name="Tab 1 page"></app-explore-container>
_15
</ion-content>

We have stored data in our vault. The next step is to get the data back out of the vault.

Get a Value

The first step is to add a method to our SessionVaultService that encapsulates getting the session. Checking if the vault is empty first ensures that we don't try to unlock a vault that may be locked but empty, which can happen in some cases.

src/app/core/session-value.service.ts

_38
import { Injectable } from '@angular/core';
_38
import { BrowserVault, DeviceSecurityType, Vault, VaultType } from '@ionic-enterprise/identity-vault';
_38
import { VaultFactory } from './vault.factory';
_38
import { Session } from '../models/session';
_38
_38
@Injectable({
_38
providedIn: 'root',
_38
})
_38
export class SessionVaultService {
_38
private vault: BrowserVault | Vault;
_38
_38
return this.vault.getValue<Session>('session');

In order to better illustrate the operation of the vault, we will modify the Tab1Page to display our session if one is stored.

src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_34
import { Component } from '@angular/core';
_34
import {
_34
IonButton,
_34
IonContent,
_34
IonHeader,
_34
IonItem,
_34
IonLabel,
_34
IonList,
_34
IonTitle,
_34
IonToolbar,
_34
} from '@ionic/angular/standalone';
_34
import { SessionVaultService } from '../core/session-vault.service';
_34
import { Session } from '../models/session';
_34
_34
@Component({
_34
selector: 'app-tab1',
_34
templateUrl: 'tab1.page.html',
_34
styleUrls: ['tab1.page.scss'],
_34
standalone: true,
_34
imports: [IonButton, IonContent, IonHeader, IonItem, IonLabel, IonList, IonTitle, IonToolbar],
_34
})
_34
export class Tab1Page {
_34
constructor(private sessionVault: SessionVaultService) {}
_34
_34
async storeSession(): Promise<void> {
_34
this.sessionVault.storeSession({
_34
email: 'test@ionic.io',
_34
firstName: 'Tessa',
_34
lastName: 'Testsmith',
_34
accessToken: '4abf1d79-143c-4b89-b478-19607eb5ce97',
_34
refreshToken: '565111b6-66c3-4527-9238-6ea2cc017126',
_34
});
_34
}
_34
}

The Tab1Page currently stores the session information.

src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_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
} from '@ionic/angular/standalone';
_36
import { SessionVaultService } from '../core/session-vault.service';
_36
import { Session } from '../models/session';
_36
_36
@Component({
_36
selector: 'app-tab1',
_36
templateUrl: 'tab1.page.html',
_36
styleUrls: ['tab1.page.scss'],
_36
standalone: true,
_36
imports: [IonButton, IonContent, IonHeader, IonItem, IonLabel, IonList, IonTitle, IonToolbar],
_36
})
_36
export class Tab1Page {
_36
session: Session | null = null;
_36
_36
constructor(private sessionVault: SessionVaultService) {}
_36
_36
async storeSession(): Promise<void> {
_36
this.sessionVault.storeSession({
_36
email: 'test@ionic.io',
_36
firstName: 'Tessa',
_36
lastName: 'Testsmith',
_36
accessToken: '4abf1d79-143c-4b89-b478-19607eb5ce97',
_36
refreshToken: '565111b6-66c3-4527-9238-6ea2cc017126',
_36
});
_36
}
_36
}

Add a session property.

src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_40
import { Component, OnInit } from '@angular/core';
_40
import {
_40
IonButton,
_40
IonContent,
_40
IonHeader,
_40
IonItem,
_40
IonLabel,
_40
IonList,
_40
IonTitle,
_40
IonToolbar,
_40
} from '@ionic/angular/standalone';
_40
import { SessionVaultService } from '../core/session-vault.service';
_40
import { Session } from '../models/session';
_40
_40
@Component({
_40
selector: 'app-tab1',
_40
templateUrl: 'tab1.page.html',
_40
styleUrls: ['tab1.page.scss'],
_40
standalone: true,
_40
imports: [IonButton, IonContent, IonHeader, IonItem, IonLabel, IonList, IonTitle, IonToolbar],
_40
})
_40
export class Tab1Page implements OnInit {
_40
session: Session | null = null;
_40
_40
constructor(private sessionVault: SessionVaultService) {}
_40
_40
async ngOnInit() {
_40
this.session = await this.sessionVault.getSession();
_40
}
_40
_40
async storeSession(): Promise<void> {
_40
this.sessionVault.storeSession({
_40
email: 'test@ionic.io',
_40
firstName: 'Tessa',
_40
lastName: 'Testsmith',
_40
accessToken: '4abf1d79-143c-4b89-b478-19607eb5ce97',
_40
refreshToken: '565111b6-66c3-4527-9238-6ea2cc017126',
_40
});
_40
}
_40
}

Get the session when the page is initialized.

src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_41
import { Component, OnInit } from '@angular/core';
_41
import {
_41
IonButton,
_41
IonContent,
_41
IonHeader,
_41
IonItem,
_41
IonLabel,
_41
IonList,
_41
IonTitle,
_41
IonToolbar,
_41
} from '@ionic/angular/standalone';
_41
import { SessionVaultService } from '../core/session-vault.service';
_41
import { Session } from '../models/session';
_41
_41
@Component({
_41
selector: 'app-tab1',
_41
templateUrl: 'tab1.page.html',
_41
styleUrls: ['tab1.page.scss'],
_41
standalone: true,
_41
imports: [IonButton, IonContent, IonHeader, IonItem, IonLabel, IonList, IonTitle, IonToolbar],
_41
})
_41
export class Tab1Page implements OnInit {
_41
session: Session | null = null;
_41
_41
constructor(private sessionVault: SessionVaultService) {}
_41
_41
async ngOnInit() {
_41
this.session = await this.sessionVault.getSession();
_41
}
_41
_41
async storeSession(): Promise<void> {
_41
this.sessionVault.storeSession({
_41
email: 'test@ionic.io',
_41
firstName: 'Tessa',
_41
lastName: 'Testsmith',
_41
accessToken: '4abf1d79-143c-4b89-b478-19607eb5ce97',
_41
refreshToken: '565111b6-66c3-4527-9238-6ea2cc017126',
_41
});
_41
this.session = await this.sessionVault.getSession();
_41
}
_41
}

Also get the session immediately after it is stored.

src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_29
<ion-header [translucent]="true">
_29
<ion-toolbar>
_29
<ion-title> Tab 1 </ion-title>
_29
</ion-toolbar>
_29
</ion-header>
_29
_29
<ion-content [fullscreen]="true">
_29
<ion-header collapse="condense">
_29
<ion-toolbar>
_29
<ion-title size="large">Tab 1</ion-title>
_29
</ion-toolbar>
_29
</ion-header>
_29
_29
<ion-list>
_29
<ion-item>
_29
<ion-label>
_29
<ion-button expand="block" (click)="storeSession()">Store</ion-button>
_29
</ion-label>
_29
</ion-item>
_29
<ion-item>
_29
<div>
_29
<div>{{session?.email}}</div>
_29
<div>{{session?.firstName}} {{session?.lastName}}</div>
_29
<div>{{session?.accessToken}}</div>
_29
<div>{{session?.refreshToken}}</div>
_29
</div>
_29
</ion-item>
_29
</ion-list>
_29
</ion-content>

In the page's template, add a div to display the session.

The Tab1Page currently stores the session information.

Add a session property.

Get the session when the page is initialized.

Also get the session immediately after it is stored.

In the page's template, add a div to display the session.

src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_34
import { Component } from '@angular/core';
_34
import {
_34
IonButton,
_34
IonContent,
_34
IonHeader,
_34
IonItem,
_34
IonLabel,
_34
IonList,
_34
IonTitle,
_34
IonToolbar,
_34
} from '@ionic/angular/standalone';
_34
import { SessionVaultService } from '../core/session-vault.service';
_34
import { Session } from '../models/session';
_34
_34
@Component({
_34
selector: 'app-tab1',
_34
templateUrl: 'tab1.page.html',
_34
styleUrls: ['tab1.page.scss'],
_34
standalone: true,
_34
imports: [IonButton, IonContent, IonHeader, IonItem, IonLabel, IonList, IonTitle, IonToolbar],
_34
})
_34
export class Tab1Page {
_34
constructor(private sessionVault: SessionVaultService) {}
_34
_34
async storeSession(): Promise<void> {
_34
this.sessionVault.storeSession({
_34
email: 'test@ionic.io',
_34
firstName: 'Tessa',
_34
lastName: 'Testsmith',
_34
accessToken: '4abf1d79-143c-4b89-b478-19607eb5ce97',
_34
refreshToken: '565111b6-66c3-4527-9238-6ea2cc017126',
_34
});
_34
}
_34
}

We now have a way to store and retrieve the session. When you first run the application, the session area will be blank. When you press the Store button you will see the session information on the page. If you restart the application, you will see the session information.

If you would like to clear the session information at this point, remove the application from your device (physical or simulated) and re-install it. In the web, you can close the running tab and open new one.

Next we will see how to remove this data from within our application.

Remove the Session from the Vault

The vault has two different methods that we can use to remove the data:

  • clear: Clear all of the data stored in the vault and remove the vault from the keystore / keychain.
    • This operation does not require the vault to be unlocked.
    • This operation will remove the existing vault from the keychain / keystore.
    • Subsequent operations on the vault such as storing a new session will not require the vault to be unlocked since the vault had been removed.
    • Use this method if your vault stores a single logical entity, even if it uses multiple entries to do so.
  • removeValue: Clear just the data stored with the specified key.
    • This operation does require the vault to be unlocked.
    • This operation will not remove the existing vault from the keychain / keystore even though the vault may be empty.
    • Subsequent operations on the vault such as storing a new session may require the vault to be unlocked since the vault had been removed.
    • Use this method if your vault stores multiple logical entities.

Note: We will address locking and unlocking a vault later in this tutorial.

Our vault stores session information. Having a single vault that stores only the session information is the best-practice for this type of data, and it is the practice we are using here. Thus we will use the clear() method to clear the session.

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

_42
import { Injectable } from '@angular/core';
_42
import { BrowserVault, DeviceSecurityType, Vault, VaultType } from '@ionic-enterprise/identity-vault';
_42
import { VaultFactory } from './vault.factory';
_42
import { Session } from '../models/session';
_42
_42
@Injectable({
_42
providedIn: 'root',
_42
async clearSession(): Promise<void> {

Modify src/app/tab1/tab1.page.ts and src/app/tab1/tab1.page.html to have a "Clear" button.

src/app/tab1/tab1.page.ts

_46
import { Component, OnInit } from '@angular/core';
_46
import {
_46
IonButton,
_46
IonContent,
_46
IonHeader,
_46
IonItem,
_46
IonLabel,
_46
this.session = await this.sessionVault.getSession();

src/app/tab1/tab1.page.html

_34
<ion-header [translucent]="true">
_34
<ion-toolbar>
_34
<ion-title> Tab 1 </ion-title>
_34
</ion-toolbar>
_34
</ion-header>
_34
_34
<ion-content [fullscreen]="true">
_34
<ion-button expand="block" (click)="clear()">Clear</ion-button>

Update the Vault Type

We are currently using a "Secure Storage" vault, but there are several other vault types. In this section, we will explore the DeviceSecurity, InMemory, and SecureStorage types.

Setting the Vault Type

We can use the vault's updateConfig() method to change the type of vault that the application is using..

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

_42
import { Injectable } from '@angular/core';
_42
import { BrowserVault, DeviceSecurityType, Vault, VaultType } from '@ionic-enterprise/identity-vault';
_42
import { Session } from '../models/session';
_42
import { VaultFactory } from './vault.factory';
_42
_42
@Injectable({
_42
providedIn: 'root',
_42
})
_42
export class SessionVaultService {
_42
private vault: BrowserVault | Vault;
_42
_42
constructor() {
_42
this.vault = VaultFactory.create();
_42
}
_42
_42
async initialize(): Promise<void> {
_42
try {
_42
await this.vault.initialize({
_42
key: 'io.ionic.gettingstartediv',
_42
type: VaultType.SecureStorage,
_42
deviceSecurityType: DeviceSecurityType.None,
_42
});
_42
} catch (e: unknown) {
_42
await this.vault.clear();
_42
}
_42
}
_42
_42
async storeSession(session: Session): Promise<void> {
_42
this.vault.setValue('session', session);
_42
}
_42
_42
async getSession(): Promise<Session | null> {
_42
if (await this.vault.isEmpty()) {
_42
return null;
_42
}
_42
return this.vault.getValue<Session>('session');
_42
}
_42
_42
async clearSession(): Promise<void> {
_42
await this.vault.clear();
_42
}
_42
}

Here is the src/app/core/session-vault.service.ts that we have created thus far.

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

_44
import { Injectable } from '@angular/core';
_44
import { BrowserVault, DeviceSecurityType, Vault, VaultType } from '@ionic-enterprise/identity-vault';
_44
import { Session } from '../models/session';
_44
import { VaultFactory } from './vault.factory';
_44
_44
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_44
_44
@Injectable({
_44
providedIn: 'root',
_44
})
_44
export class SessionVaultService {
_44
private vault: BrowserVault | Vault;
_44
_44
constructor() {
_44
this.vault = VaultFactory.create();
_44
}
_44
_44
async initialize(): Promise<void> {
_44
try {
_44
await this.vault.initialize({
_44
key: 'io.ionic.gettingstartediv',
_44
type: VaultType.SecureStorage,
_44
deviceSecurityType: DeviceSecurityType.None,
_44
});
_44
} catch (e: unknown) {
_44
await this.vault.clear();
_44
}
_44
}
_44
_44
async storeSession(session: Session): Promise<void> {
_44
this.vault.setValue('session', session);
_44
}
_44
_44
async getSession(): Promise<Session | null> {
_44
if (await this.vault.isEmpty()) {
_44
return null;
_44
}
_44
return this.vault.getValue<Session>('session');
_44
}
_44
_44
async clearSession(): Promise<void> {
_44
await this.vault.clear();
_44
}
_44
}

The UnlockMode specifies the logical combinations of settings we wish to support within our application.

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

_46
import { Injectable } from '@angular/core';
_46
import { BrowserVault, DeviceSecurityType, Vault, VaultType } from '@ionic-enterprise/identity-vault';
_46
import { Session } from '../models/session';
_46
import { VaultFactory } from './vault.factory';
_46
_46
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_46
_46
@Injectable({
_46
providedIn: 'root',
_46
})
_46
export class SessionVaultService {
_46
private vault: BrowserVault | Vault;
_46
_46
constructor() {
_46
this.vault = VaultFactory.create();
_46
}
_46
_46
async initialize(): Promise<void> {
_46
try {
_46
await this.vault.initialize({
_46
key: 'io.ionic.gettingstartediv',
_46
type: VaultType.SecureStorage,
_46
deviceSecurityType: DeviceSecurityType.None,
_46
});
_46
} catch (e: unknown) {
_46
await this.vault.clear();
_46
}
_46
}
_46
_46
async storeSession(session: Session): Promise<void> {
_46
this.vault.setValue('session', session);
_46
}
_46
_46
async getSession(): Promise<Session | null> {
_46
if (await this.vault.isEmpty()) {
_46
return null;
_46
}
_46
return this.vault.getValue<Session>('session');
_46
}
_46
_46
async clearSession(): Promise<void> {
_46
await this.vault.clear();
_46
}
_46
_46
async updateUnlockMode(mode: UnlockMode): Promise<void> {}
_46
}

Add an updateUnlockMode() method to the class. Take a single argument for the mode.

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

_56
import { Injectable } from '@angular/core';
_56
import {
_56
BrowserVault,
_56
DeviceSecurityType,
_56
IdentityVaultConfig,
_56
Vault,
_56
VaultType,
_56
} from '@ionic-enterprise/identity-vault';
_56
import { Session } from '../models/session';
_56
import { VaultFactory } from './vault.factory';
_56
_56
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_56
_56
@Injectable({
_56
providedIn: 'root',
_56
})
_56
export class SessionVaultService {
_56
private vault: BrowserVault | Vault;
_56
_56
constructor() {
_56
this.vault = VaultFactory.create();
_56
}
_56
_56
async initialize(): Promise<void> {
_56
try {
_56
await this.vault.initialize({
_56
key: 'io.ionic.gettingstartediv',
_56
type: VaultType.SecureStorage,
_56
deviceSecurityType: DeviceSecurityType.None,
_56
});
_56
} catch (e: unknown) {
_56
await this.vault.clear();
_56
}
_56
}
_56
_56
async storeSession(session: Session): Promise<void> {
_56
this.vault.setValue('session', session);
_56
}
_56
_56
async getSession(): Promise<Session | null> {
_56
if (await this.vault.isEmpty()) {
_56
return null;
_56
}
_56
return this.vault.getValue<Session>('session');
_56
}
_56
_56
async clearSession(): Promise<void> {
_56
await this.vault.clear();
_56
}
_56
_56
async updateUnlockMode(mode: UnlockMode): Promise<void> {
_56
await this.vault.updateConfig({
_56
...(this.vault.config as IdentityVaultConfig),
_56
});
_56
}
_56
}

The vault's updateConfig() method takes a full vault configuration object, so pass our current config. Cast it to IdentityVaultConfig to signify that we know the value is not undefined at this point.

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

_63
import { Injectable } from '@angular/core';
_63
import {
_63
BrowserVault,
_63
DeviceSecurityType,
_63
IdentityVaultConfig,
_63
Vault,
_63
VaultType,
_63
} from '@ionic-enterprise/identity-vault';
_63
import { Session } from '../models/session';
_63
import { VaultFactory } from './vault.factory';
_63
_63
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_63
_63
@Injectable({
_63
providedIn: 'root',
_63
})
_63
export class SessionVaultService {
_63
private vault: BrowserVault | Vault;
_63
_63
constructor() {
_63
this.vault = VaultFactory.create();
_63
}
_63
_63
async initialize(): Promise<void> {
_63
try {
_63
await this.vault.initialize({
_63
key: 'io.ionic.gettingstartediv',
_63
type: VaultType.SecureStorage,
_63
deviceSecurityType: DeviceSecurityType.None,
_63
});
_63
} catch (e: unknown) {
_63
await this.vault.clear();
_63
}
_63
}
_63
_63
async storeSession(session: Session): Promise<void> {
_63
this.vault.setValue('session', session);
_63
}
_63
_63
async getSession(): Promise<Session | null> {
_63
if (await this.vault.isEmpty()) {
_63
return null;
_63
}
_63
return this.vault.getValue<Session>('session');
_63
}
_63
_63
async clearSession(): Promise<void> {
_63
await this.vault.clear();
_63
}
_63
_63
async updateUnlockMode(mode: UnlockMode): Promise<void> {
_63
const type =
_63
mode === 'BiometricsWithPasscode'
_63
? VaultType.DeviceSecurity
_63
: mode === 'InMemory'
_63
? VaultType.InMemory
_63
: VaultType.SecureStorage;
_63
await this.vault.updateConfig({
_63
...(this.vault.config as IdentityVaultConfig),
_63
type,
_63
});
_63
}
_63
}

Update the type based on the specified mode.

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

_65
import { Injectable } from '@angular/core';
_65
import {
_65
BrowserVault,
_65
DeviceSecurityType,
_65
IdentityVaultConfig,
_65
Vault,
_65
VaultType,
_65
} from '@ionic-enterprise/identity-vault';
_65
import { Session } from '../models/session';
_65
import { VaultFactory } from './vault.factory';
_65
_65
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_65
_65
@Injectable({
_65
providedIn: 'root',
_65
})
_65
export class SessionVaultService {
_65
private vault: BrowserVault | Vault;
_65
_65
constructor() {
_65
this.vault = VaultFactory.create();
_65
}
_65
_65
async initialize(): Promise<void> {
_65
try {
_65
await this.vault.initialize({
_65
key: 'io.ionic.gettingstartediv',
_65
type: VaultType.SecureStorage,
_65
deviceSecurityType: DeviceSecurityType.None,
_65
});
_65
} catch (e: unknown) {
_65
await this.vault.clear();
_65
}
_65
}
_65
_65
async storeSession(session: Session): Promise<void> {
_65
this.vault.setValue('session', session);
_65
}
_65
_65
async getSession(): Promise<Session | null> {
_65
if (await this.vault.isEmpty()) {
_65
return null;
_65
}
_65
return this.vault.getValue<Session>('session');
_65
}
_65
_65
async clearSession(): Promise<void> {
_65
await this.vault.clear();
_65
}
_65
_65
async updateUnlockMode(mode: UnlockMode): Promise<void> {
_65
const type =
_65
mode === 'BiometricsWithPasscode'
_65
? VaultType.DeviceSecurity
_65
: mode === 'InMemory'
_65
? VaultType.InMemory
_65
: VaultType.SecureStorage;
_65
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_65
await this.vault.updateConfig({
_65
...(this.vault.config as IdentityVaultConfig),
_65
type,
_65
deviceSecurityType,
_65
});
_65
}
_65
}

Update the deviceSecurityType based on the value of the type.

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

_66
import { Injectable } from '@angular/core';
_66
import {
_66
BrowserVault,
_66
DeviceSecurityType,
_66
IdentityVaultConfig,
_66
Vault,
_66
VaultType,
_66
} from '@ionic-enterprise/identity-vault';
_66
import { Session } from '../models/session';
_66
import { VaultFactory } from './vault.factory';
_66
_66
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_66
_66
@Injectable({
_66
providedIn: 'root',
_66
})
_66
export class SessionVaultService {
_66
private vault: BrowserVault | Vault;
_66
_66
constructor() {
_66
this.vault = VaultFactory.create();
_66
}
_66
_66
async initialize(): Promise<void> {
_66
try {
_66
await this.vault.initialize({
_66
key: 'io.ionic.gettingstartediv',
_66
type: VaultType.SecureStorage,
_66
deviceSecurityType: DeviceSecurityType.None,
_66
});
_66
} catch (e: unknown) {
_66
await this.vault.clear();
_66
await this.updateUnlockMode('SecureStorage');
_66
}
_66
}
_66
_66
async storeSession(session: Session): Promise<void> {
_66
this.vault.setValue('session', session);
_66
}
_66
_66
async getSession(): Promise<Session | null> {
_66
if (await this.vault.isEmpty()) {
_66
return null;
_66
}
_66
return this.vault.getValue<Session>('session');
_66
}
_66
_66
async clearSession(): Promise<void> {
_66
await this.vault.clear();
_66
}
_66
_66
async updateUnlockMode(mode: UnlockMode): Promise<void> {
_66
const type =
_66
mode === 'BiometricsWithPasscode'
_66
? VaultType.DeviceSecurity
_66
: mode === 'InMemory'
_66
? VaultType.InMemory
_66
: VaultType.SecureStorage;
_66
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_66
await this.vault.updateConfig({
_66
...(this.vault.config as IdentityVaultConfig),
_66
type,
_66
deviceSecurityType,
_66
});
_66
}
_66
}

Update the deviceSecurityType based on the value of the type.

Here is the src/app/core/session-vault.service.ts that we have created thus far.

The UnlockMode specifies the logical combinations of settings we wish to support within our application.

Add an updateUnlockMode() method to the class. Take a single argument for the mode.

The vault's updateConfig() method takes a full vault configuration object, so pass our current config. Cast it to IdentityVaultConfig to signify that we know the value is not undefined at this point.

Update the type based on the specified mode.

Update the deviceSecurityType based on the value of the type.

Update the deviceSecurityType based on the value of the type.

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

_42
import { Injectable } from '@angular/core';
_42
import { BrowserVault, DeviceSecurityType, Vault, VaultType } from '@ionic-enterprise/identity-vault';
_42
import { Session } from '../models/session';
_42
import { VaultFactory } from './vault.factory';
_42
_42
@Injectable({
_42
providedIn: 'root',
_42
})
_42
export class SessionVaultService {
_42
private vault: BrowserVault | Vault;
_42
_42
constructor() {
_42
this.vault = VaultFactory.create();
_42
}
_42
_42
async initialize(): Promise<void> {
_42
try {
_42
await this.vault.initialize({
_42
key: 'io.ionic.gettingstartediv',
_42
type: VaultType.SecureStorage,
_42
deviceSecurityType: DeviceSecurityType.None,
_42
});
_42
} catch (e: unknown) {
_42
await this.vault.clear();
_42
}
_42
}
_42
_42
async storeSession(session: Session): Promise<void> {
_42
this.vault.setValue('session', session);
_42
}
_42
_42
async getSession(): Promise<Session | null> {
_42
if (await this.vault.isEmpty()) {
_42
return null;
_42
}
_42
return this.vault.getValue<Session>('session');
_42
}
_42
_42
async clearSession(): Promise<void> {
_42
await this.vault.clear();
_42
}
_42
}

Why the UnlockMode?

One natural question from above may be "why create an UnlockMode type when you can pass in the VaultType and figure things out from there?" The answer to that is that any time you incorporate a third-party library into your code like this, you should create an "adapter" service that utilizes the library within the domain of your application.

This has two major benefits:

  1. It insulates the rest of the application from change. If the next major version of Identity Vault has breaking changes that need to be addressed, the only place in the code they need to be addressed is in this service. The rest of the code continues to interact with the vault via the interface defined by the service.
  2. It reduces vendor tie-in, making it easier to swap to different libraries in the future if need be.

The ultimate goal is for the only modules in the application directly import from @ionic-enterprise/identity-vault to be services like this one that encapsulate operations on a vault.

Setting the deviceSecurityType Value

The deviceSecurityType property only applies when the type is set to DeviceSecurity. We could use any of the following DeviceSecurityType values:

  • Biometrics: Use the system's default biometric option to unlock the vault.
  • SystemPasscode: Use the system's designated system passcode (PIN, Pattern, etc.) to unlock the vault.
  • Both: Primarily use the biometric hardware to unlock the vault, but use the system passcode as a backup for cases where the biometric hardware is not configured or biometric authentication has failed.

For our application, we will just keep it simple and use Both when using DeviceSecurity vault. This is a very versatile option and makes the most sense for most applications.

With vault types other than DeviceSecurity, always use DeviceSecurityType.None.

Update the Tab1Page

We can now add some buttons to the Tab1Page in order to try out the different vault types. Update the src/app/tab1/tab1.page.ts and src/app/tab1/tab1.page.html as shown below.

src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_50
import { Component, OnInit } from '@angular/core';
_50
import {
_50
IonButton,
_50
IonContent,
_50
IonHeader,
_50
IonItem,
_50
IonLabel,
_50
IonList,
_50
IonTitle,
_50
IonToolbar,
_50
} from '@ionic/angular/standalone';
_50
import { SessionVaultService, UnlockMode } from '../core/session-vault.service';
_50
import { Session } from '../models/session';
_50
_50
@Component({
_50
selector: 'app-home',
_50
templateUrl: 'home.page.html',
_50
styleUrls: ['home.page.scss'],
_50
standalone: true,
_50
imports: [IonButton, IonContent, IonHeader, IonItem, IonLabel, IonList, IonTitle, IonToolbar],
_50
})
_50
export class Tab1Page implements OnInit {
_50
session: Session | null = null;
_50
_50
constructor(private sessionVault: SessionVaultService) {}
_50
_50
async ngOnInit() {
_50
this.session = await this.sessionVault.getSession();
_50
}
_50
_50
async storeSession(): Promise<void> {
_50
this.sessionVault.storeSession({
_50
email: 'test@ionic.io',
_50
firstName: 'Tessa',
_50
lastName: 'Testsmith',
_50
accessToken: '4abf1d79-143c-4b89-b478-19607eb5ce97',
_50
refreshToken: '565111b6-66c3-4527-9238-6ea2cc017126',
_50
});
_50
this.session = await this.sessionVault.getSession();
_50
}
_50
_50
async clear(): Promise<void> {
_50
await this.sessionVault.clearSession();
_50
this.session = await this.sessionVault.getSession();
_50
}
_50
_50
async changeUnlockMode(mode: UnlockMode) {
_50
await this.sessionVault.updateUnlockMode(mode);
_50
}
_50
}

Build the application and run it on a device upon which you have biometrics enabled. Perform the following steps for each type of vault:

  1. Press the "Store" button to put data in the vault.
  2. Choose a vault type via one of the "Use" buttons.
  3. Close the application (do not just put it in the background, but close it).
  4. Restart the application.

You should see the following results:

  • "Use Biometrics": On an iPhone with FaceID, this will fail. We will fix that next. On all other devices, however, a biometric prompt will be displayed to unlock the vault. The data will be displayed once the vault is unlocked.
  • "Use In Memory": The data is no longer set. As the name implies, there is no persistence of this data.
  • "Use Secure Storage": The stored data is displayed without unlocking.

Native Configuration

If you tried the tests above on an iPhone with Face ID, your app should have crashed upon restarting when using a biometric vault. If you run npx cap sync you will see what is missing.


_10
[warn] Configuration required for @ionic-enterprise/identity-vault.
_10
Add the following to Info.plist:
_10
<key>NSFaceIDUsageDescription</key>
_10
<string>Use Face ID to authenticate yourself and login</string>

Open the ios/App/App/Info.plist file and add the specified configuration. The actual string value can be anything you want, but the key must be NSFaceIDUsageDescription.

ios/App/App/Info.plist

_51
<?xml version="1.0" encoding="UTF-8"?>
_51
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
_51
<plist version="1.0">
_51
<dict>
_51
<key>CFBundleDevelopmentRegion</key>
_51
<string>Use Face ID to authenticate yourself and login</string>

Biometrics should work on the iPhone at this point.

Lock and Unlock the Vault

Going forward we will begin exploring functionality that only works when the application is run on a device. As such, you should begin testing on a device instead of using the development server.

Right now, the only way to "lock" the vault is to close the application. In this section we will look at a couple of other ways to lock the vault as well as ways to unlock it.

Manually Locking the Vault

In src/app/core/session-vault.service.ts, wrap the vault's lock() method so we can use it in our Tab1Page.

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

_69
import { Injectable } from '@angular/core';
_69
import {
_69
BrowserVault,
_69
DeviceSecurityType,
_69
IdentityVaultConfig,
_69
Vault,
_69
VaultType,
_69
} from '@ionic-enterprise/identity-vault';
_69
import { Session } from '../models/session';
_69
async lock(): Promise<void> {

Add a lock button in src/app/tab1/tab1.page.ts and src/app/tab1/tab1.page.html.

src/app/tab1/tab1.page.ts

_55
import { Component, OnInit } from '@angular/core';
_55
import {
_55
IonButton,
_55
IonContent,
_55
IonHeader,
_55
IonItem,
_55
IonLabel,
_55
await this.sessionVault.lock();

src/app/tab1/tab1.page.html

_60
<ion-header [translucent]="true">
_60
<ion-toolbar>
_60
<ion-title> Tab 1 </ion-title>
_60
</ion-toolbar>
_60
</ion-header>
_60
_60
<ion-content [fullscreen]="true">
_60
<ion-button expand="block" color="warning" (click)="lock()">Lock</ion-button>

When we press the "Lock" button, the session data is no longer displayed. The actual status of the vault depends on the last "unlock mode" button pressed prior to locking the vault.

  • "Use Biometrics": The vault has been locked and the session data will not be accessible until it is unlocked.
  • "Use In Memory": The session data no longer exists.
  • "Use In Secure Storage": The session data is in the vault, but is not locked.

Unlocking the Vault

To verify the behaviors noted above, you need to be able to unlock the vault. To do this you can use the vault's unlock() method or you can perform an operation that requires the vault to be unlocked. When we unlock the vault, we need to restore the session data in our page, so we can just use our getSession() method. When it calls the vault's getValue(), the getValue() will attempt to unlock the vault.

Add the following code to src/app/tab1/tab1.page.ts and src/app/tab1/tab1.page.html:

src/app/tab1/tab1.page.ts

_59
import { Component, OnInit } from '@angular/core';
_59
import {
_59
IonButton,
_59
IonContent,
_59
IonHeader,
_59
IonItem,
_59
IonLabel,
_59
this.session = await this.sessionVault.getSession();

src/app/tab1/tab1.page.html

_65
<ion-header [translucent]="true">
_65
<ion-toolbar>
_65
<ion-title> Tab 1 </ion-title>
_65
</ion-toolbar>
_65
</ion-header>
_65
_65
<ion-content [fullscreen]="true">
_65
<ion-button expand="block" color="warning" (click)="unlock()">Unlock</ion-button>

We can now use the "Lock" and "Unlock" buttons to verify the behavior of each of our unlock modes.

Locking in the Background

We can manually lock our vault, but it would be nice if the vault locked for us automatically. This can be accomplished by setting lockAfterBackgrounded when we initialize the vault. This will lock the vault when the application is resumed if the app was backgrounded for the configured amount of time. Here we are setting it to 2000 milliseconds.

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

_71
import { Injectable } from '@angular/core';
_71
import {
_71
BrowserVault,
_71
DeviceSecurityType,
_71
IdentityVaultConfig,
_71
Vault,
_71
VaultType,
_71
} from '@ionic-enterprise/identity-vault';
_71
import { Session } from '../models/session';
_71
lockAfterBackgrounded: 2000,

If you now switch the app to use a mode that locks, like Biometrics, and then put the app in the background for two seconds or more, the vault will lock even though you won't really know it.

One way to deal with this is to create an Observable in our service that indicates if the vault is currently locked or unlocked. We can then subscribe to that Observable in our Tab1Page and remove the session data from our page when the vault locks.

src/app/core/session-vault.service.ts
src/app/tab1/tab1.page.ts

_70
import { Injectable } from '@angular/core';
_70
import {
_70
BrowserVault,
_70
DeviceSecurityType,
_70
IdentityVaultConfig,
_70
Vault,
_70
VaultType,
_70
} from '@ionic-enterprise/identity-vault';
_70
import { Session } from '../models/session';
_70
import { VaultFactory } from './vault.factory';
_70
_70
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_70
_70
@Injectable({
_70
providedIn: 'root',
_70
})
_70
export class SessionVaultService {
_70
private vault: BrowserVault | Vault;
_70
_70
constructor() {
_70
this.vault = VaultFactory.create();
_70
}
_70
_70
async initialize(): Promise<void> {
_70
try {
_70
await this.vault.initialize({
_70
key: 'io.ionic.gettingstartediv',
_70
type: VaultType.SecureStorage,
_70
deviceSecurityType: DeviceSecurityType.None,
_70
lockAfterBackgrounded: 2000,
_70
});
_70
} catch (e: unknown) {
_70
await this.vault.clear();
_70
await this.updateUnlockMode('SecureStorage');
_70
}
_70
}
_70
_70
async storeSession(session: Session): Promise<void> {
_70
this.vault.setValue('session', session);
_70
}
_70
_70
async getSession(): Promise<Session | null> {
_70
if (await this.vault.isEmpty()) {
_70
return null;
_70
}
_70
}
_70
_70
async clearSession(): Promise<void> {
_70
await this.vault.clear();
_70
}
_70
_70
async lock(): Promise<void> {
_70
await this.vault.lock();
_70
}
_70
_70
async updateUnlockMode(mode: UnlockMode): Promise<void> {
_70
const type =
_70
mode === 'BiometricsWithPasscode'
_70
? VaultType.DeviceSecurity
_70
: mode === 'InMemory'
_70
? VaultType.InMemory
_70
: VaultType.SecureStorage;
_70
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_70
await this.vault.updateConfig({
_70
...(this.vault.config as IdentityVaultConfig),
_70
type,
_70
deviceSecurityType,
_70
});
_70
}
_70
}

Here are our SessionVaultService and Tab1Page classes so far.

src/app/core/session-vault.service.ts
src/app/tab1/tab1.page.ts

_78
import { Injectable } from '@angular/core';
_78
import {
_78
BrowserVault,
_78
DeviceSecurityType,
_78
IdentityVaultConfig,
_78
Vault,
_78
VaultType,
_78
} from '@ionic-enterprise/identity-vault';
_78
import { Session } from '../models/session';
_78
import { VaultFactory } from './vault.factory';
_78
import { Observable, Subject } from 'rxjs';
_78
_78
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_78
_78
@Injectable({
_78
providedIn: 'root',
_78
})
_78
export class SessionVaultService {
_78
private vault: BrowserVault | Vault;
_78
private lockedSubject: Subject<boolean>;
_78
_78
constructor() {
_78
this.vault = VaultFactory.create();
_78
this.lockedSubject = new Subject<boolean>();
_78
}
_78
_78
get locked$(): Observable<boolean> {
_78
return this.lockedSubject.asObservable();
_78
}
_78
_78
async initialize(): Promise<void> {
_78
try {
_78
await this.vault.initialize({
_78
key: 'io.ionic.gettingstartediv',
_78
type: VaultType.SecureStorage,
_78
deviceSecurityType: DeviceSecurityType.None,
_78
lockAfterBackgrounded: 2000,
_78
});
_78
} catch (e: unknown) {
_78
await this.vault.clear();
_78
await this.updateUnlockMode('SecureStorage');
_78
}
_78
}
_78
_78
async storeSession(session: Session): Promise<void> {
_78
this.vault.setValue('session', session);
_78
}
_78
_78
async getSession(): Promise<Session | null> {
_78
if (await this.vault.isEmpty()) {
_78
return null;
_78
}
_78
return this.vault.getValue<Session>('session');
_78
}
_78
_78
async clearSession(): Promise<void> {
_78
await this.vault.clear();
_78
}
_78
_78
async lock(): Promise<void> {
_78
await this.vault.lock();
_78
}
_78
_78
async updateUnlockMode(mode: UnlockMode): Promise<void> {
_78
const type =
_78
mode === 'BiometricsWithPasscode'
_78
? VaultType.DeviceSecurity
_78
: mode === 'InMemory'
_78
? VaultType.InMemory
_78
: VaultType.SecureStorage;
_78
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_78
await this.vault.updateConfig({
_78
...(this.vault.config as IdentityVaultConfig),
_78
type,
_78
deviceSecurityType,
_78
});
_78
}
_78
}

Create a private Subject (lockedSubject) and expose it publicly as an Observable (locked$).

src/app/core/session-vault.service.ts
src/app/tab1/tab1.page.ts

_81
import { Injectable } from '@angular/core';
_81
import {
_81
BrowserVault,
_81
DeviceSecurityType,
_81
IdentityVaultConfig,
_81
Vault,
_81
VaultType,
_81
} from '@ionic-enterprise/identity-vault';
_81
import { Session } from '../models/session';
_81
import { VaultFactory } from './vault.factory';
_81
import { Observable, Subject } from 'rxjs';
_81
_81
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_81
_81
@Injectable({
_81
providedIn: 'root',
_81
})
_81
export class SessionVaultService {
_81
private vault: BrowserVault | Vault;
_81
private lockedSubject: Subject<boolean>;
_81
_81
constructor() {
_81
this.vault = VaultFactory.create();
_81
this.lockedSubject = new Subject<boolean>();
_81
}
_81
_81
get locked$(): Observable<boolean> {
_81
return this.lockedSubject.asObservable();
_81
}
_81
_81
async initialize(): Promise<void> {
_81
try {
_81
await this.vault.initialize({
_81
key: 'io.ionic.gettingstartediv',
_81
type: VaultType.SecureStorage,
_81
deviceSecurityType: DeviceSecurityType.None,
_81
lockAfterBackgrounded: 2000,
_81
});
_81
} catch (e: unknown) {
_81
await this.vault.clear();
_81
await this.updateUnlockMode('SecureStorage');
_81
}
_81
_81
this.vault.onLock(() => this.lockedSubject.next(true));
_81
this.vault.onUnlock(() => this.lockedSubject.next(false));
_81
}
_81
_81
async storeSession(session: Session): Promise<void> {
_81
this.vault.setValue('session', session);
_81
}
_81
_81
async getSession(): Promise<Session | null> {
_81
if (await this.vault.isEmpty()) {
_81
return null;
_81
}
_81
return this.vault.getValue<Session>('session');
_81
}
_81
_81
async clearSession(): Promise<void> {
_81
await this.vault.clear();
_81
}
_81
_81
async lock(): Promise<void> {
_81
await this.vault.lock();
_81
}
_81
_81
async updateUnlockMode(mode: UnlockMode): Promise<void> {
_81
const type =
_81
mode === 'BiometricsWithPasscode'
_81
? VaultType.DeviceSecurity
_81
: mode === 'InMemory'
_81
? VaultType.InMemory
_81
: VaultType.SecureStorage;
_81
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_81
await this.vault.updateConfig({
_81
...(this.vault.config as IdentityVaultConfig),
_81
type,
_81
deviceSecurityType,
_81
});
_81
}
_81
}

Emit the lock status using the onLock and onUnlock events. Never access the vault in pause or resume events. Always use the onLock and onUnlock events to control interactions with the vault upon lock or unlock.

src/app/core/session-vault.service.ts
src/app/tab1/tab1.page.ts

_61
import { Component, OnInit } from '@angular/core';
_61
import {
_61
IonButton,
_61
IonContent,
_61
IonHeader,
_61
IonItem,
_61
IonLabel,
_61
IonList,
_61
IonTitle,
_61
IonToolbar,
_61
} from '@ionic/angular/standalone';
_61
import { SessionVaultService, UnlockMode } from '../core/session-vault.service';
_61
import { Session } from '../models/session';
_61
_61
@Component({
_61
selector: 'app-home',
_61
templateUrl: 'home.page.html',
_61
styleUrls: ['home.page.scss'],
_61
standalone: true,
_61
imports: [IonButton, IonContent, IonHeader, IonItem, IonLabel, IonList, IonTitle, IonToolbar],
_61
})
_61
export class Tab1Page implements OnInit {
_61
session: Session | null = null;
_61
_61
constructor(private sessionVault: SessionVaultService) {
_61
this.sessionVault.locked$.subscribe((lock) => (this.session = lock ? null : this.session));
_61
}
_61
_61
async ngOnInit() {
_61
this.session = await this.sessionVault.getSession();
_61
}
_61
_61
async storeSession(): Promise<void> {
_61
this.sessionVault.storeSession({
_61
email: 'test@ionic.io',
_61
firstName: 'Tessa',
_61
lastName: 'Testsmith',
_61
accessToken: '4abf1d79-143c-4b89-b478-19607eb5ce97',
_61
refreshToken: '565111b6-66c3-4527-9238-6ea2cc017126',
_61
});
_61
this.session = await this.sessionVault.getSession();
_61
}
_61
_61
async clear(): Promise<void> {
_61
await this.sessionVault.clearSession();
_61
this.session = await this.sessionVault.getSession();
_61
}
_61
_61
async changeUnlockMode(mode: UnlockMode) {
_61
await this.sessionVault.updateUnlockMode(mode);
_61
}
_61
_61
async lock(): Promise<void> {
_61
this.session = null;
_61
await this.sessionVault.lock();
_61
}
_61
_61
async unlock(): Promise<void> {
_61
this.session = await this.sessionVault.getSession();
_61
}
_61
}

Subscribe to the Observable in the Tab1Page and clear page's session data if the vault locks.

src/app/core/session-vault.service.ts
src/app/tab1/tab1.page.ts

_67
import { Component, OnDestroy, OnInit } from '@angular/core';
_67
import {
_67
IonButton,
_67
IonContent,
_67
IonHeader,
_67
IonItem,
_67
IonLabel,
_67
IonList,
_67
IonTitle,
_67
IonToolbar,
_67
} from '@ionic/angular/standalone';
_67
import { SessionVaultService, UnlockMode } from '../core/session-vault.service';
_67
import { Session } from '../models/session';
_67
import { Subscription } from 'rxjs';
_67
_67
@Component({
_67
selector: 'app-home',
_67
templateUrl: 'home.page.html',
_67
styleUrls: ['home.page.scss'],
_67
standalone: true,
_67
imports: [IonButton, IonContent, IonHeader, IonItem, IonLabel, IonList, IonTitle, IonToolbar],
_67
})
_67
export class Tab1Page implements OnInit, OnDestroy {
_67
private subscription: Subscription;
_67
session: Session | null = null;
_67
_67
constructor(private sessionVault: SessionVaultService) {
_67
this.subscription = this.sessionVault.locked$.subscribe((lock) => (this.session = lock ? null : this.session));
_67
}
_67
_67
async ngOnInit() {
_67
this.session = await this.sessionVault.getSession();
_67
}
_67
_67
ngOnDestroy() {
_67
this.subscription.unsubscribe();
_67
}
_67
_67
async storeSession(): Promise<void> {
_67
this.sessionVault.storeSession({
_67
email: 'test@ionic.io',
_67
firstName: 'Tessa',
_67
lastName: 'Testsmith',
_67
accessToken: '4abf1d79-143c-4b89-b478-19607eb5ce97',
_67
refreshToken: '565111b6-66c3-4527-9238-6ea2cc017126',
_67
});
_67
this.session = await this.sessionVault.getSession();
_67
}
_67
_67
async clear(): Promise<void> {
_67
await this.sessionVault.clearSession();
_67
this.session = await this.sessionVault.getSession();
_67
}
_67
_67
async changeUnlockMode(mode: UnlockMode) {
_67
await this.sessionVault.updateUnlockMode(mode);
_67
}
_67
_67
async lock(): Promise<void> {
_67
this.session = null;
_67
await this.sessionVault.lock();
_67
}
_67
_67
async unlock(): Promise<void> {
_67
this.session = await this.sessionVault.getSession();
_67
}
_67
}

For proper housekeeping, we should save a reference to the subscription so we can unsubscribe when the page is destroyed.

Here are our SessionVaultService and Tab1Page classes so far.

Create a private Subject (lockedSubject) and expose it publicly as an Observable (locked$).

Emit the lock status using the onLock and onUnlock events. Never access the vault in pause or resume events. Always use the onLock and onUnlock events to control interactions with the vault upon lock or unlock.

Subscribe to the Observable in the Tab1Page and clear page's session data if the vault locks.

For proper housekeeping, we should save a reference to the subscription so we can unsubscribe when the page is destroyed.

src/app/core/session-vault.service.ts
src/app/tab1/tab1.page.ts

_70
import { Injectable } from '@angular/core';
_70
import {
_70
BrowserVault,
_70
DeviceSecurityType,
_70
IdentityVaultConfig,
_70
Vault,
_70
VaultType,
_70
} from '@ionic-enterprise/identity-vault';
_70
import { Session } from '../models/session';
_70
import { VaultFactory } from './vault.factory';
_70
_70
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_70
_70
@Injectable({
_70
providedIn: 'root',
_70
})
_70
export class SessionVaultService {
_70
private vault: BrowserVault | Vault;
_70
_70
constructor() {
_70
this.vault = VaultFactory.create();
_70
}
_70
_70
async initialize(): Promise<void> {
_70
try {
_70
await this.vault.initialize({
_70
key: 'io.ionic.gettingstartediv',
_70
type: VaultType.SecureStorage,
_70
deviceSecurityType: DeviceSecurityType.None,
_70
lockAfterBackgrounded: 2000,
_70
});
_70
} catch (e: unknown) {
_70
await this.vault.clear();
_70
await this.updateUnlockMode('SecureStorage');
_70
}
_70
}
_70
_70
async storeSession(session: Session): Promise<void> {
_70
this.vault.setValue('session', session);
_70
}
_70
_70
async getSession(): Promise<Session | null> {
_70
if (await this.vault.isEmpty()) {
_70
return null;
_70
}
_70
}
_70
_70
async clearSession(): Promise<void> {
_70
await this.vault.clear();
_70
}
_70
_70
async lock(): Promise<void> {
_70
await this.vault.lock();
_70
}
_70
_70
async updateUnlockMode(mode: UnlockMode): Promise<void> {
_70
const type =
_70
mode === 'BiometricsWithPasscode'
_70
? VaultType.DeviceSecurity
_70
: mode === 'InMemory'
_70
? VaultType.InMemory
_70
: VaultType.SecureStorage;
_70
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_70
await this.vault.updateConfig({
_70
...(this.vault.config as IdentityVaultConfig),
_70
type,
_70
deviceSecurityType,
_70
});
_70
}
_70
}

Architectural Considerations

Construction vs. Initialization

Have a look at the src/app/core/session-vault.service.ts file. Notice that it is very intentional about separating construction and initialization. This is very important.

Identity Vault allows you to pass the configuration object via the new Vault(cfg) constructor. This, however, will make asynchronous calls which makes construction indeterminate. This is bad.

Always use a pattern of:

  • Construct the vault via new Vault() (default constructor, no configuration).
  • Pass the configuration to the vault.initialize(cfg) function.
  • Perform the initialization itself via the APP_INITIALIZER and make sure that the code is properly awaiting its completion.

Control Unlocking on Startup and Navigation

Our code is currently automatically unlocking the vault upon startup due to the code in ngOnInit(). This is OK for our app, but it could be a problem if we had situations where multiple calls to get data from a locked vault all happened simultaneously. For example if we have AuthGuards and HTTP Interceptors also trying to access the vault at the same time. Always make sure you are controlling the vault lock status in such situations to ensure that only one unlock attempt is being made at a time.

important

If you are using lockAfterBackgrounded do not interact with the vault, directly or indirectly, 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.

We will see various strategies for this in later tutorials. You can also refer to our troubleshooting guide for further guidance.

Initial Vault Type Configuration

When we first initialize the vault we use the following configuration:


_10
await this.vault.initialize({
_10
key: 'io.ionic.gettingstartediv',
_10
type: VaultType.SecureStorage,
_10
deviceSecurityType: DeviceSecurityType.None,
_10
lockAfterBackgrounded: 2000,
_10
});

It is important to note that this is an initial configuration. Once a vault is created, it (and its current configuration) persist between invocations of the application. Thus, if the configuration of the vault is updated by the application, the updated configuration will be read when the application is reopened. For example, if the lockAfterBackgrounded has been updated to 5000 milliseconds, then when we start the application again with the vault already existing, lockAfterBackgrounded will remain set to 5000 milliseconds. The configuration we pass here is only used if we later destroy and re-create this vault.

Notice that we are specifying a type of VaultType.SecureStorage. It is best to use either VaultType.SecureStorage or VaultType.InMemeory when calling initialize() to avoid the potential of creating a vault of a type that cannot be supported. We can always update the type later after and the updated type will "stick." We want to start, however, with an option that will always word regardless of the device's configuration.

Single Vault vs Multiple Vaults

Identity Vault is ideal for storing small chunks of data such as authentication information or encryption keys. Our sample application contains a single vault. However, it may make sense to use multiple vaults within your application's architecture.

Ask yourself the following questions:

  1. What type of data is stored?
  2. Under what conditions should the data be available to the application?

Let's say the application is storing the following information:

  • The authentication session data.
  • A set of encryption keys.

You can use a single vault to store this data if all of the following are true:

  • You only want to access the vault via a single service.
  • The requirements for when the data is accessible is identical.

You should use multiple vaults to store this data if any of the following are true:

  • You logically want to use different services for different types of data.
  • You logically would like to use different services to access different types of data.
  • The requirements for when the data is accessible differs in some way. For example, perhaps the authentication information is locked behind a biometric key while access to the encryption keys requires a custom set in-app code to be entered.

If you decide to use multiple vaults, a best-practice is to create a separate service for each vault. That is, in the interest of proper organization within your code, each vault service should only manage a single vault.