Skip to main content
Version: 5.0

Using the Device API

Overview

When we created our other Identity Vault tutorial applications we implemented several pieces of functionality without taking the capabilities and current configuration of the device into account. We will address that now with the help of the Device API.

In this tutorial we will:

  • Examine the methods available in the Device API.
  • Disable options based on the current device capabilities and configurations.
  • Show the privacy screen when the application is in the task switcher.
  • Progressively enhance the vault type when the user logs in.
  • Handle the iOS Face ID permissions before setting the vault to use biometric unlock.

Let's Code

This tutorial builds upon the application created when doing the startup strategies tutorial. If you have the code from when you performed that tutorial, then you are ready to go. If you need the code you can make a copy from our GitHub repository.

The Device API

The functions in the Device API can be separated into three different categories:

  • Capabilities: The values returned by these functions are defined by the capabilities of the device.
  • Configuration and Status: The values returned by these functions are determined by the device's current configuration and/or status.
  • Procedures: These functions perform some kind of device related process such as setting a configuration value or displaying a prompt.

We will use the Tab2Page to test out how these functions work.

Capabilities

The "capabilities" functions return information about the capabilities of the current device. The values returned by these functions are constant for a given device.

  • getAvailableHardware: Resolves an array of the types of biometric hardware that a device supports.
  • hasSecureHardware: Resolves true if the device has hardware dedicated to storing secure data. Most modern devices do.
  • isBiometricsSupported: Resolves true if the device supports some form of biometrics regardless of whether or not biometrics is configured. Most modern devices support biometrics.

Update the Tab2Page to display the results of these methods.

src/views/Tab2Page.vue

_70
<template>
_70
<ion-page>
_70
<ion-header>
_70
<ion-toolbar>
_70
<ion-title>Tab 2</ion-title>
_70
</ion-toolbar>
_70
</ion-header>
_70
<ion-content :fullscreen="true">
_70
<ion-header collapse="condense">
_70
<ion-toolbar>
_70
<ion-title size="large">Tab 2</ion-title>
_70
</ion-toolbar>
_70
</ion-header>
_70
_70
<ion-list>
_70
<ion-list-header>
_70
<ion-label>Capabilities</ion-label>
_70
</ion-list-header>
_70
<ion-item>
_70
<ion-label>Secure Hardware</ion-label>
_70
<ion-note slot="end">{{hasSecureHardware}}</ion-note>
_70
</ion-item>
_70
<ion-item>
_70
<ion-label>Biometrics Supported</ion-label>
_70
<ion-note slot="end">{{isBiometricsSupported}}</ion-note>
_70
</ion-item>
_70
<ion-item>
_70
<div>
_70
<p>Available Biometric Hardware:</p>
_70
<ul v-if="availableHardware?.length">
_70
<li v-for="h of availableHardware" :key="h">{{h}}</li>
_70
</ul>
_70
<ul v-if="availableHardware?.length === 0">
_70
<li>None</li>
_70
</ul>
_70
</div>
_70
</ion-item>
_70
</ion-list>
_70
</ion-content>
_70
</ion-page>
_70
</template>
_70
_70
<script setup lang="ts">
_70
import {
_70
IonContent,
_70
IonHeader,
_70
IonItem,
_70
IonLabel,
_70
IonList,
_70
IonListHeader,
_70
IonNote,
_70
IonPage,
_70
IonTitle,
_70
IonToolbar,
_70
} from '@ionic/vue';
_70
import { ref } from 'vue';
_70
import { Device } from '@ionic-enterprise/identity-vault';
_70
_70
const hasSecureHardware = ref<boolean>(false);
_70
const isBiometricsSupported = ref<boolean>(false);
_70
const availableHardware = ref<Array<string>>([]);
_70
_70
const initialize = async (): Promise<void> => {
_70
hasSecureHardware.value = await Device.hasSecureHardware();
_70
isBiometricsSupported.value = await Device.isBiometricsSupported();
_70
availableHardware.value = await Device.getAvailableHardware();
_70
}
_70
_70
initialize();
_70
</script>

Note that these value are consistent on a particular device, regardless of whether or not biometrics is currently configured on that device.

warning
Results may vary on a device where the user does not have a system passcode set.

Configuration and Status

  • getBiometricStrengthLevel: On iOS, this function always resolves to strong. On Android, the result depends on the Biometrics Classes of whichever biometrics are currently configured on the device. If at least one of the configured biometric options is Class 3, it will return strong, otherwise it will return weak.
  • isBiometricsAllowed: On iOS Face ID based devices this method resolves to Prompt if the user needs to be asked for permission to use Face ID, Denied if the user has declined permission, and Granted if the user has granted permission. On all other devices, which do not require such permissions, this method always resolves to Granted.
  • isBiometricsEnabled: Resolves true if biometrics have been configured for the current user, false otherwise.
  • isHideScreenOnBackgroundEnabled: Resolves true if the "hide screen" will be displayed when the app is placed in the background, false otherwise.
  • isLockedOutOfBiometrics: Resolves true if the user is locked out of biometrics after too many failed attempts. On iOS, this information may be known upon app launch. On Android, an attempt to unlock with the current app session needs to be performed before this can be known.
  • isSystemPasscodeSet: Resolves true if the user has established a system passcode that can be used to unlock the app, false otherwise.

Update the Tab2Page to display the results of these methods.

src/views/Tab2Page.vue

_111
<template>
_111
<ion-page>
_111
<ion-header>
_111
<ion-toolbar>
_111
<ion-title>Tab 2</ion-title>
_111
</ion-toolbar>
_111
</ion-header>
_111
<ion-content :fullscreen="true">
_111
<ion-header collapse="condense">
_111
<ion-toolbar>
_111
<ion-title size="large">Tab 2</ion-title>
_111
</ion-toolbar>
_111
</ion-header>
_111
_111
<ion-list>
_111
<ion-list-header>
_111
<ion-label>Capabilities</ion-label>
_111
</ion-list-header>
_111
<ion-item>
_111
<ion-label>Secure Hardware</ion-label>
_111
<ion-note slot="end">{{hasSecureHardware}}</ion-note>
_111
</ion-item>
_111
<ion-item>
_111
<ion-label>Biometrics Supported</ion-label>
_111
<ion-note slot="end">{{isBiometricsSupported}}</ion-note>
_111
</ion-item>
_111
<ion-item>
_111
<div>
_111
<p>Available Biometric Hardware:</p>
_111
<ul v-if="availableHardware?.length">
_111
<li v-for="h of availableHardware" :key="h">{{h}}</li>
_111
</ul>
_111
<ul v-if="availableHardware?.length === 0">
_111
<li>None</li>
_111
</ul>
_111
</div>
_111
</ion-item>
_111
<ion-list-header>
_111
<ion-label>Configuration and Status</ion-label>
_111
</ion-list-header>
_111
<ion-item>
_111
<ion-label>Biometric Strength Level</ion-label>
_111
<ion-note slot="end">{{biometricStrengthLevel}}</ion-note>
_111
</ion-item>
_111
<ion-item>
_111
<ion-label>Biometric Allowed</ion-label>
_111
<ion-note slot="end">{{isBiometricsAllowed}}</ion-note>
_111
</ion-item>
_111
<ion-item>
_111
<ion-label>Biometrics Enabled</ion-label>
_111
<ion-note slot="end">{{isBiometricsEnabled}}</ion-note>
_111
</ion-item>
_111
<ion-item>
_111
<ion-label>Hide Screen Enabled</ion-label>
_111
<ion-note slot="end">{{isHideScreenOnBackgroundEnabled}}</ion-note>
_111
</ion-item>
_111
<ion-item>
_111
<ion-label>Locked Out of Biometrics</ion-label>
_111
<ion-note slot="end">{{isLockedOutOfBiometrics}}</ion-note>
_111
</ion-item>
_111
<ion-item>
_111
<ion-label>System Passcode Set</ion-label>
_111
<ion-note slot="end">{{isSystemPasscodeSet}}</ion-note>
_111
</ion-item>
_111
</ion-list>
_111
</ion-content>
_111
</ion-page>
_111
</template>
_111
_111
<script setup lang="ts">
_111
import {
_111
isHideScreenOnBackgroundEnabled.value = await Device.isHideScreenOnBackgroundEnabled();

Build the application and install it on a variety of devices. Then modify the configuration of the biometrics on those devices to see how the values change.

Procedures

  • setHideScreenOnBackground: Set whether or not the interface is obscured when the application is placed in the background.
  • showBiometricPrompt: Show a biometric prompt to the user. This method will resolve when the user dismisses the prompt by successfully providing biometrics, and will reject if they cancel. On iOS devices with Face ID, this method will also ask for permission as needed.

Add a couple of buttons to the Tab2Page to call these methods.

src/views/Tab2Page.vue

_145
<template>
_145
<ion-page>
_145
<ion-header>
_145
<ion-toolbar>
_145
<ion-title>Tab 2</ion-title>
_145
</ion-toolbar>
_145
</ion-header>
_145
<ion-content :fullscreen="true">
_145
<ion-header collapse="condense">
_145
<ion-toolbar>
_145
<ion-title size="large">Tab 2</ion-title>
_145
</ion-toolbar>
_145
</ion-header>
_145
_145
<ion-list>
_145
<ion-list-header>
_145
<ion-label>Capabilities</ion-label>
_145
</ion-list-header>
_145
<ion-item>
_145
<ion-label>Secure Hardware</ion-label>
_145
<ion-note slot="end">{{hasSecureHardware}}</ion-note>
_145
</ion-item>
_145
<ion-item>
_145
<ion-label>Biometrics Supported</ion-label>
_145
<ion-note slot="end">{{isBiometricsSupported}}</ion-note>
_145
</ion-item>
_145
<ion-item>
_145
<div>
_145
<p>Available Biometric Hardware:</p>
_145
<ul v-if="availableHardware?.length">
_145
<li v-for="h of availableHardware" :key="h">{{h}}</li>
_145
</ul>
_145
<ul v-if="availableHardware?.length === 0">
_145
<li>None</li>
_145
</ul>
_145
</div>
_145
</ion-item>
_145
<ion-list-header>
_145
<ion-label>Configuration and Status</ion-label>
_145
</ion-list-header>
_145
<ion-item>
_145
<ion-label>Biometric Strength Level</ion-label>
_145
<ion-note slot="end">{{biometricStrengthLevel}}</ion-note>
_145
</ion-item>
_145
<ion-item>
_145
<ion-label>Biometric Allowed</ion-label>
_145
<ion-note slot="end">{{isBiometricsAllowed}}</ion-note>
_145
</ion-item>
_145
<ion-item>
_145
<ion-label>Biometrics Enabled</ion-label>
_145
<ion-note slot="end">{{isBiometricsEnabled}}</ion-note>
_145
</ion-item>
_145
<ion-item>
_145
<ion-label>Hide Screen Enabled</ion-label>
_145
<ion-note slot="end">{{isHideScreenOnBackgroundEnabled}}</ion-note>
_145
</ion-item>
_145
<ion-item>
_145
<ion-label>Locked Out of Biometrics</ion-label>
_145
<ion-note slot="end">{{isLockedOutOfBiometrics}}</ion-note>
_145
</ion-item>
_145
<ion-item>
_145
<ion-label>System Passcode Set</ion-label>
_145
<ion-note slot="end">{{isSystemPasscodeSet}}</ion-note>
_145
</ion-item>
_145
<ion-list-header>
_145
<ion-label>Actions</ion-label>
_145
</ion-list-header>
_145
<ion-item>
_145
<ion-label>
_145
<ion-button expand="block" :disabled="!isBiometricsEnabled" @click="showBiometricPrompt"
_145
>Show Biometric Prompt</ion-button
_145
>
_145
</ion-label>
_145
</ion-item>
_145
<ion-item>
_145
<ion-label>
_145
<ion-button expand="block" @click="toggleHideScreenOnBackground"
_145
>{{isHideScreenOnBackgroundEnabled ? 'Disable' : 'Enable'}} Security Screen</ion-button
_145
>
_145
</ion-label>
_145
</ion-item>
_145
</ion-list>

Build the code and install it on a variety of different types of devices to see how the procedures behave.

Common Uses of the Device API

Now that we have seen an overview of the Device API let's have a look at some common tasks that we can perform using the methods from this API. In this section, we look at how to perform the following tasks:

  • Enabling or disabling various functionality based on the capabilities of the device.
  • Setting the app to show the "privacy screen" when the app is being shown in the app switcher.
  • Progressively enhance the vault to use the most secure options available on the device.
  • Managing permissions on iOS devices that use Face ID.

Enable / Disable Functionality

We can use various methods within the Device API to enable or disable the setting of various preferences, settings, or workflows based on whether or not the current device has biometrics enabled or not. For example, if the application has a preference to toggle biometric authentication on and off, we could hide or disable the toggle for that setting based on the value of isBiometricsEnabled().

src/views/Tab1Page.vue

_101
<template>
_101
<ion-page>
_101
<ion-header>
_101
<ion-toolbar>
_101
<ion-title>Tab 1</ion-title>
_101
</ion-toolbar>
_101
</ion-header>
_101
<ion-content :fullscreen="true">
_101
<ion-header collapse="condense">
_101
<ion-toolbar>
_101
<ion-title size="large">Tab 1</ion-title>
_101
</ion-toolbar>
_101
</ion-header>
_101
_101
<ion-list>
_101
<ion-item>
_101
<ion-label>
_101
<ion-button expand="block" color="danger" @click="logoutClicked" data-testid="logout">Logout</ion-button>
_101
</ion-label>
_101
</ion-item>
_101
<ion-item>
_101
<ion-label>
_101
<ion-button
_101
expand="block"
_101
color="secondary"
_101
@click="updateUnlockMode('BiometricsWithPasscode')"
_101
:disabled="disableBiometrics"
_101
data-testid="use-biometrics"
_101
>Use Biometrics</ion-button
_101
>
_101
</ion-label>
_101
</ion-item>
_101
<ion-item>
_101
<ion-label>
_101
<ion-button
_101
expand="block"
_101
color="secondary"
_101
@click="updateUnlockMode('InMemory')"
_101
data-testid="use-in-memory"
_101
>Use In Memory</ion-button
_101
>
_101
</ion-label>
_101
</ion-item>
_101
<ion-item>
_101
<ion-label>
_101
<ion-button
_101
expand="block"
_101
color="secondary"
_101
@click="updateUnlockMode('SecureStorage')"
_101
data-testid="use-secure-storage"
_101
>Use Secure Storage</ion-button
_101
>
_101
</ion-label>
_101
</ion-item>
_101
<ion-item>
_101
<div data-testid="session">
_101
<div>{{ session?.email }}</div>
_101
<div>{{ session?.firstName }} {{ session?.lastName }}</div>
_101
<div>{{ session?.accessToken }}</div>
_101
<div>{{ session?.refreshToken }}</div>
_101
</div>
_101
</ion-item>
_101
</ion-list>
_101
</ion-content>
_101
</ion-page>
_101
</template>
_101
_101
<script setup lang="ts">
_101
import { useAuthentication } from '@/composables/authentication';
_101
import { useSessionVault } from '@/composables/session-vault';
_101
import {
_101
IonButton,
_101
IonContent,
_101
IonHeader,
_101
disableBiometrics.value = !(await Device.isBiometricsEnabled());

Show the Privacy Screen

Many applications that use Identity Vault also display sensitive data that the user may not want shown if the application is shown in the app switcher. Identity Vault has a "privacy screen" feature that will obscure the page contents in this situation. On Android, a gray or black page will be shown instead. On iOS, the splash screen will be displayed.

In the Tab2Page we show how to use Device.setHideScreenOnBackground() to toggle this feature on and off. Most applications will either want this feature on or off without a toggle. In such cases, it is best to set the feature however you wish upon application startup.

src/main.ts

_36
import { createApp } from 'vue';
_36
import { useSessionVault } from '@/composables/session-vault';
_36
import App from './App.vue';
_36
import router from './router';
_36
_36
import { IonicVue } from '@ionic/vue';
_36
import { Device } from '@ionic-enterprise/identity-vault';
_36
_36
/* Core CSS required for Ionic components to work properly */
_36
import '@ionic/vue/css/core.css';
_36
_36
/* Basic CSS for apps built with Ionic */
_36
import '@ionic/vue/css/normalize.css';
_36
import '@ionic/vue/css/structure.css';
_36
import '@ionic/vue/css/typography.css';
_36
_36
/* Optional CSS utils that can be commented out */
_36
import '@ionic/vue/css/padding.css';
_36
import '@ionic/vue/css/float-elements.css';
_36
import '@ionic/vue/css/text-alignment.css';
_36
import '@ionic/vue/css/text-transformation.css';
_36
import '@ionic/vue/css/flex-utils.css';
_36
import '@ionic/vue/css/display.css';
_36
_36
/* Theme variables */
_36
import './theme/variables.css';
_36

You could also put the Device.setHideScreenOnBackground(true) call within the initializeVault().then() callback and then await it before mounting the app. Doing so is fine, but not necessary.

note

You may also be tempted to include the Device.setHideScreenOnBackground(true) call in the initializeVault() function itself. It is a best-practice, however, to keep the code separate from the vault code to avoid confusion in cases where the application needs to use multiple vaults.

Progressively Enhance the Vault

It is a best practice to initially create the vault with a configuration that all devices can support and then enhance the vault's security either based on user preferences or the capabilities of the device. In this section, we will examine how to automatically enhance the security based on the capabilities of the device.

For our application, we would like to use biometrics with a system passcode backup if possible. Otherwise, we will force the user to log in each time.

  • If the vault is already storing a session, leave it alone.
  • If the vault is not storing a session:
    • Use biometrics with a system passcode backup if it is set up.
    • Use a system passcode if biometrics is not set up.
    • If neither biometrics nor a system passcode are set, force the user to login in each time.
src/composables/session-vault.ts

_94
import { useVaultFactory } from '@/composables/vault-factory';
_94
import { Session } from '@/models/session';
_94
import {
_94
BrowserVault,
_94
DeviceSecurityType,
_94
IdentityVaultConfig,
_94
Vault,
_94
VaultType,
_94
} from '@ionic-enterprise/identity-vault';
_94
import { ref } from 'vue';
_94
_94
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_94
_94
const { createVault } = useVaultFactory();
_94
const vault: Vault | BrowserVault = createVault();
_94
const session = ref<Session | null>(null);
_94
_94
const initializeVault = async (): Promise<void> => {
_94
try {
_94
await vault.initialize({
_94
key: 'io.ionic.gettingstartediv',
_94
type: VaultType.SecureStorage,
_94
deviceSecurityType: DeviceSecurityType.None,
_94
lockAfterBackgrounded: 2000,
_94
});
_94
} catch (e: unknown) {
_94
await vault.clear();
_94
await updateUnlockMode('SecureStorage');
_94
}
_94
_94
vault.onLock(() => (session.value = null));
_94
};
_94
_94
const storeSession = async (s: Session): Promise<void> => {
_94
vault.setValue('session', s);
_94
session.value = s;
_94
};
_94
_94
const getSession = async (): Promise<void> => {
_94
if (await vault.isEmpty()) {
_94
session.value = null;
_94
} else {
_94
session.value = await vault.getValue<Session>('session');
_94
}
_94
};
_94
_94
const clearSession = async (): Promise<void> => {
_94
await vault.clear();
_94
session.value = null;
_94
};
_94
_94
const lockSession = async (): Promise<void> => {
_94
await vault.lock();
_94
session.value = null;
_94
};
_94
_94
const unlockSession = async (): Promise<void> => {
_94
await vault.unlock();
_94
session.value = await vault.getValue<Session>('session');
_94
};
_94
_94
const sessionIsLocked = async (): Promise<boolean> => {
_94
return (
_94
vault.config?.type !== VaultType.SecureStorage &&
_94
vault.config?.type !== VaultType.InMemory &&
_94
!(await vault.isEmpty()) &&
_94
(await vault.isLocked())
_94
);
_94
};
_94
_94
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_94
const type =
_94
mode === 'BiometricsWithPasscode'
_94
? VaultType.DeviceSecurity
_94
: mode === 'InMemory'
_94
? VaultType.InMemory
_94
: VaultType.SecureStorage;
_94
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_94
await vault.updateConfig({
_94
...(vault.config as IdentityVaultConfig),
_94
type,
_94
deviceSecurityType,
_94
});
_94
};
_94
_94
export const useSessionVault = (): any => ({
_94
clearSession,
_94
getSession,
_94
initializeVault,
_94
lockSession,
_94
session,
_94
storeSession,
_94
updateUnlockMode,
_94
});

Our app is written to start with a Secure Storage type vault.

src/composables/session-vault.ts

_95
import { useVaultFactory } from '@/composables/vault-factory';
_95
import { Session } from '@/models/session';
_95
import {
_95
BrowserVault,
_95
Device,
_95
DeviceSecurityType,
_95
IdentityVaultConfig,
_95
Vault,
_95
VaultType,
_95
} from '@ionic-enterprise/identity-vault';
_95
import { ref } from 'vue';
_95
_95
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_95
_95
const { createVault } = useVaultFactory();
_95
const vault: Vault | BrowserVault = createVault();
_95
const session = ref<Session | null>(null);
_95
_95
const initializeVault = async (): Promise<void> => {
_95
try {
_95
await vault.initialize({
_95
key: 'io.ionic.gettingstartediv',
_95
type: VaultType.InMemory,
_95
deviceSecurityType: DeviceSecurityType.None,
_95
lockAfterBackgrounded: 30000,
_95
});
_95
} catch (e: unknown) {
_95
await vault.clear();
_95
await updateUnlockMode('InMemory');
_95
}
_95
_95
vault.onLock(() => (session.value = null));
_95
};
_95
_95
const storeSession = async (s: Session): Promise<void> => {
_95
vault.setValue('session', s);
_95
session.value = s;
_95
};
_95
_95
const getSession = async (): Promise<void> => {
_95
if (await vault.isEmpty()) {
_95
session.value = null;
_95
} else {
_95
session.value = await vault.getValue<Session>('session');
_95
}
_95
};
_95
_95
const clearSession = async (): Promise<void> => {
_95
await vault.clear();
_95
session.value = null;
_95
};
_95
_95
const lockSession = async (): Promise<void> => {
_95
await vault.lock();
_95
session.value = null;
_95
};
_95
_95
const unlockSession = async (): Promise<void> => {
_95
await vault.unlock();
_95
session.value = await vault.getValue<Session>('session');
_95
};
_95
_95
const sessionIsLocked = async (): Promise<boolean> => {
_95
return (
_95
vault.config?.type !== VaultType.SecureStorage &&
_95
vault.config?.type !== VaultType.InMemory &&
_95
!(await vault.isEmpty()) &&
_95
(await vault.isLocked())
_95
);
_95
};
_95
_95
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_95
const type =
_95
mode === 'BiometricsWithPasscode'
_95
? VaultType.DeviceSecurity
_95
: mode === 'InMemory'
_95
? VaultType.InMemory
_95
: VaultType.SecureStorage;
_95
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_95
await vault.updateConfig({
_95
...(vault.config as IdentityVaultConfig),
_95
type,
_95
deviceSecurityType,
_95
});
_95
};
_95
_95
export const useSessionVault = (): any => ({
_95
clearSession,
_95
getSession,
_95
initializeVault,
_95
lockSession,
_95
session,
_95
storeSession,
_95
updateUnlockMode,
_95
});

Import the Device class so we can use the API.

Change the type to be InMemory and increase the background lock time from 2 seconds to 30 seconds.

src/composables/session-vault.ts

_103
import { useVaultFactory } from '@/composables/vault-factory';
_103
import { Session } from '@/models/session';
_103
import {
_103
BrowserVault,
_103
Device,
_103
DeviceSecurityType,
_103
IdentityVaultConfig,
_103
Vault,
_103
VaultType,
_103
} from '@ionic-enterprise/identity-vault';
_103
import { ref } from 'vue';
_103
_103
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_103
_103
const { createVault } = useVaultFactory();
_103
const vault: Vault | BrowserVault = createVault();
_103
const session = ref<Session | null>(null);
_103
_103
const initializeVault = async (): Promise<void> => {
_103
try {
_103
await vault.initialize({
_103
key: 'io.ionic.gettingstartediv',
_103
type: VaultType.InMemory,
_103
deviceSecurityType: DeviceSecurityType.None,
_103
lockAfterBackgrounded: 30000,
_103
});
_103
} catch (e: unknown) {
_103
await vault.clear();
_103
await updateUnlockMode('InMemory');
_103
}
_103
_103
await enhanceVault();
_103
_103
vault.onLock(() => (session.value = null));
_103
};
_103
_103
const enhanceVault = async (): Promise<void> => {
_103
if (!(await this.vault.isEmpty())) {
_103
return;
_103
}
_103
};
_103
_103
const storeSession = async (s: Session): Promise<void> => {
_103
vault.setValue('session', s);
_103
session.value = s;
_103
};
_103
_103
const getSession = async (): Promise<void> => {
_103
if (await vault.isEmpty()) {
_103
session.value = null;
_103
} else {
_103
session.value = await vault.getValue<Session>('session');
_103
}
_103
};
_103
_103
const clearSession = async (): Promise<void> => {
_103
await vault.clear();
_103
session.value = null;
_103
};
_103
_103
const lockSession = async (): Promise<void> => {
_103
await vault.lock();
_103
session.value = null;
_103
};
_103
_103
const unlockSession = async (): Promise<void> => {
_103
await vault.unlock();
_103
session.value = await vault.getValue<Session>('session');
_103
};
_103
_103
const sessionIsLocked = async (): Promise<boolean> => {
_103
return (
_103
vault.config?.type !== VaultType.SecureStorage &&
_103
vault.config?.type !== VaultType.InMemory &&
_103
!(await vault.isEmpty()) &&
_103
(await vault.isLocked())
_103
);
_103
};
_103
_103
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_103
const type =
_103
mode === 'BiometricsWithPasscode'
_103
? VaultType.DeviceSecurity
_103
: mode === 'InMemory'
_103
? VaultType.InMemory
_103
: VaultType.SecureStorage;
_103
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_103
await vault.updateConfig({
_103
...(vault.config as IdentityVaultConfig),
_103
type,
_103
deviceSecurityType,
_103
});
_103
};
_103
_103
export const useSessionVault = (): any => ({
_103
clearSession,
_103
getSession,
_103
initializeVault,
_103
lockSession,
_103
session,
_103
storeSession,
_103
updateUnlockMode,
_103
});

Add an enhanceVault() function. Per our first requirement, the vault should not be changed if it is already in use.

Enhance the vault as part of the initialization process.

src/composables/session-vault.ts

_109
import { useVaultFactory } from '@/composables/vault-factory';
_109
import { Session } from '@/models/session';
_109
import {
_109
BrowserVault,
_109
Device,
_109
DeviceSecurityType,
_109
IdentityVaultConfig,
_109
Vault,
_109
VaultType,
_109
} from '@ionic-enterprise/identity-vault';
_109
import { ref } from 'vue';
_109
_109
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_109
_109
const { createVault } = useVaultFactory();
_109
const vault: Vault | BrowserVault = createVault();
_109
const session = ref<Session | null>(null);
_109
_109
const initializeVault = async (): Promise<void> => {
_109
try {
_109
await vault.initialize({
_109
key: 'io.ionic.gettingstartediv',
_109
type: VaultType.InMemory,
_109
deviceSecurityType: DeviceSecurityType.None,
_109
lockAfterBackgrounded: 30000,
_109
});
_109
} catch (e: unknown) {
_109
await vault.clear();
_109
await updateUnlockMode('InMemory');
_109
}
_109
_109
await enhanceVault();
_109
_109
vault.onLock(() => (session.value = null));
_109
};
_109
_109
const enhanceVault = async (): Promise<void> => {
_109
if (!(await this.vault.isEmpty())) {
_109
return;
_109
}
_109
_109
if (await Device.isSystemPasscodeSet()) {
_109
await updateUnlockMode('BiometricsWithPasscode');
_109
} else {
_109
await updateUnlockMode('InMemory');
_109
}
_109
};
_109
_109
const storeSession = async (s: Session): Promise<void> => {
_109
vault.setValue('session', s);
_109
session.value = s;
_109
};
_109
_109
const getSession = async (): Promise<void> => {
_109
if (await vault.isEmpty()) {
_109
session.value = null;
_109
} else {
_109
session.value = await vault.getValue<Session>('session');
_109
}
_109
};
_109
_109
const clearSession = async (): Promise<void> => {
_109
await vault.clear();
_109
session.value = null;
_109
};
_109
_109
const lockSession = async (): Promise<void> => {
_109
await vault.lock();
_109
session.value = null;
_109
};
_109
_109
const unlockSession = async (): Promise<void> => {
_109
await vault.unlock();
_109
session.value = await vault.getValue<Session>('session');
_109
};
_109
_109
const sessionIsLocked = async (): Promise<boolean> => {
_109
return (
_109
vault.config?.type !== VaultType.SecureStorage &&
_109
vault.config?.type !== VaultType.InMemory &&
_109
!(await vault.isEmpty()) &&
_109
(await vault.isLocked())
_109
);
_109
};
_109
_109
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_109
const type =
_109
mode === 'BiometricsWithPasscode'
_109
? VaultType.DeviceSecurity
_109
: mode === 'InMemory'
_109
? VaultType.InMemory
_109
: VaultType.SecureStorage;
_109
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_109
await vault.updateConfig({
_109
...(vault.config as IdentityVaultConfig),
_109
type,
_109
deviceSecurityType,
_109
});
_109
};
_109
_109
export const useSessionVault = (): any => ({
_109
clearSession,
_109
getSession,
_109
initializeVault,
_109
lockSession,
_109
session,
_109
storeSession,
_109
updateUnlockMode,
_109
});

Use the proper vault type based on whether or not a system passcode is set.

src/composables/session-vault.ts

_111
import { useVaultFactory } from '@/composables/vault-factory';
_111
import { Session } from '@/models/session';
_111
import {
_111
BrowserVault,
_111
Device,
_111
DeviceSecurityType,
_111
IdentityVaultConfig,
_111
Vault,
_111
VaultType,
_111
} from '@ionic-enterprise/identity-vault';
_111
import { ref } from 'vue';
_111
_111
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_111
_111
const { createVault } = useVaultFactory();
_111
const vault: Vault | BrowserVault = createVault();
_111
const session = ref<Session | null>(null);
_111
_111
const initializeVault = async (): Promise<void> => {
_111
try {
_111
await vault.initialize({
_111
key: 'io.ionic.gettingstartediv',
_111
type: VaultType.InMemory,
_111
deviceSecurityType: DeviceSecurityType.None,
_111
lockAfterBackgrounded: 30000,
_111
});
_111
} catch (e: unknown) {
_111
await vault.clear();
_111
await updateUnlockMode('InMemory');
_111
}
_111
_111
await enhanceVault();
_111
_111
vault.onLock(() => (session.value = null));
_111
};
_111
_111
const enhanceVault = async (): Promise<void> => {
_111
if (!(await this.vault.isEmpty())) {
_111
return;
_111
}
_111
_111
if (await Device.isSystemPasscodeSet()) {
_111
await updateUnlockMode('BiometricsWithPasscode');
_111
} else {
_111
await updateUnlockMode('InMemory');
_111
}
_111
};
_111
_111
const storeSession = async (s: Session): Promise<void> => {
_111
vault.setValue('session', s);
_111
session.value = s;
_111
};
_111
_111
const getSession = async (): Promise<void> => {
_111
if (await vault.isEmpty()) {
_111
session.value = null;
_111
} else {
_111
session.value = await vault.getValue<Session>('session');
_111
}
_111
};
_111
_111
const clearSession = async (): Promise<void> => {
_111
await vault.clear();
_111
session.value = null;
_111
};
_111
_111
const lockSession = async (): Promise<void> => {
_111
await vault.lock();
_111
session.value = null;
_111
};
_111
_111
const unlockSession = async (): Promise<void> => {
_111
await vault.unlock();
_111
session.value = await vault.getValue<Session>('session');
_111
};
_111
_111
const sessionIsLocked = async (): Promise<boolean> => {
_111
return (
_111
vault.config?.type !== VaultType.SecureStorage &&
_111
vault.config?.type !== VaultType.InMemory &&
_111
!(await vault.isEmpty()) &&
_111
(await vault.isLocked())
_111
);
_111
};
_111
_111
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_111
const type =
_111
mode === 'BiometricsWithPasscode'
_111
? VaultType.DeviceSecurity
_111
: mode === 'InMemory'
_111
? VaultType.InMemory
_111
: VaultType.SecureStorage;
_111
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_111
const lockAfterBackgrounded = type === VaultType.InMemory ? 30000 : 2000;
_111
await vault.updateConfig({
_111
...(vault.config as IdentityVaultConfig),
_111
type,
_111
deviceSecurityType,
_111
lockAfterBackgrounded,
_111
});
_111
};
_111
_111
export const useSessionVault = (): any => ({
_111
clearSession,
_111
getSession,
_111
initializeVault,
_111
lockSession,
_111
session,
_111
storeSession,
_111
updateUnlockMode,
_111
});

Modify updateUnlockMode() to adjust the lockAfterBackgrounded time based on the type of vault used.

Our app is written to start with a Secure Storage type vault.

Import the Device class so we can use the API.

Change the type to be InMemory and increase the background lock time from 2 seconds to 30 seconds.

Add an enhanceVault() function. Per our first requirement, the vault should not be changed if it is already in use.

Enhance the vault as part of the initialization process.

Use the proper vault type based on whether or not a system passcode is set.

Modify updateUnlockMode() to adjust the lockAfterBackgrounded time based on the type of vault used.

src/composables/session-vault.ts

_94
import { useVaultFactory } from '@/composables/vault-factory';
_94
import { Session } from '@/models/session';
_94
import {
_94
BrowserVault,
_94
DeviceSecurityType,
_94
IdentityVaultConfig,
_94
Vault,
_94
VaultType,
_94
} from '@ionic-enterprise/identity-vault';
_94
import { ref } from 'vue';
_94
_94
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_94
_94
const { createVault } = useVaultFactory();
_94
const vault: Vault | BrowserVault = createVault();
_94
const session = ref<Session | null>(null);
_94
_94
const initializeVault = async (): Promise<void> => {
_94
try {
_94
await vault.initialize({
_94
key: 'io.ionic.gettingstartediv',
_94
type: VaultType.SecureStorage,
_94
deviceSecurityType: DeviceSecurityType.None,
_94
lockAfterBackgrounded: 2000,
_94
});
_94
} catch (e: unknown) {
_94
await vault.clear();
_94
await updateUnlockMode('SecureStorage');
_94
}
_94
_94
vault.onLock(() => (session.value = null));
_94
};
_94
_94
const storeSession = async (s: Session): Promise<void> => {
_94
vault.setValue('session', s);
_94
session.value = s;
_94
};
_94
_94
const getSession = async (): Promise<void> => {
_94
if (await vault.isEmpty()) {
_94
session.value = null;
_94
} else {
_94
session.value = await vault.getValue<Session>('session');
_94
}
_94
};
_94
_94
const clearSession = async (): Promise<void> => {
_94
await vault.clear();
_94
session.value = null;
_94
};
_94
_94
const lockSession = async (): Promise<void> => {
_94
await vault.lock();
_94
session.value = null;
_94
};
_94
_94
const unlockSession = async (): Promise<void> => {
_94
await vault.unlock();
_94
session.value = await vault.getValue<Session>('session');
_94
};
_94
_94
const sessionIsLocked = async (): Promise<boolean> => {
_94
return (
_94
vault.config?.type !== VaultType.SecureStorage &&
_94
vault.config?.type !== VaultType.InMemory &&
_94
!(await vault.isEmpty()) &&
_94
(await vault.isLocked())
_94
);
_94
};
_94
_94
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_94
const type =
_94
mode === 'BiometricsWithPasscode'
_94
? VaultType.DeviceSecurity
_94
: mode === 'InMemory'
_94
? VaultType.InMemory
_94
: VaultType.SecureStorage;
_94
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_94
await vault.updateConfig({
_94
...(vault.config as IdentityVaultConfig),
_94
type,
_94
deviceSecurityType,
_94
});
_94
};
_94
_94
export const useSessionVault = (): any => ({
_94
clearSession,
_94
getSession,
_94
initializeVault,
_94
lockSession,
_94
session,
_94
storeSession,
_94
updateUnlockMode,
_94
});

Build the application and test it on a variety of devices using a variety of screen unlock settings. You should see the following behaviors:

  • On devices without a system passcode or biometrics the user should be forced to login any time the application is restarted or is put in the background for more than 30 seconds.
  • On devices with a system passcode but without biometrics set up, the session is stored between invocations and is unlocked via the system passcode. The application locks after 2 seconds in the background.
  • On devices with a system passcode and with biometrics set up, the session is stored between invocations and is unlocked via biometrics using the system passcode as backup. The application locks after 2 seconds in the background.
  • On all devices, if "Use Secure Storage" is selected, then a secure storage vault will be used. This can be tested by pressing "Use Secure Storage" and closing the app. When it comes back the user should still be logged in. This should repeat until the user logs out prior to closing the app.
note

We are taking advantage of the fact that DeviceSecurityType.Both uses the system passcode not only when biometrics fails, but also if biometrics is not set up at all. Perhaps we want our app to behave in a more specific manner such as any of the following:

  • Use only Biometrics with a system passcode backup only if biometrics is set up.
  • Use Biometrics without a system passcode backup, but use system passcode if biometrics is not set up.
  • Use Biometrics with a system passcode backup only if biometrics is set up. Allow the user to set a custom passcode in other circumstances.

Identity Vault and the Device API give you the flexibility to support any of those scenarios and more. Please contact us if you would like to discuss your specific scenario.

Handle iOS Permissions

On iOS devices that use Face ID, the user needs to give permission in order for the application to use their face to unlock the app. The prompt for this permission is not provided to the user until your app tries to use Face ID for the first time. As a result, our application is not asking for permission to use the user's face until the first time it tries to unlock the vault. It would be better if it asked for permission before setting the vault to use biometrics. Let's do that now.

The requirements we have from our design team are;

  • Keep the vault type logic as it is now. That is:
    • If there is a system passcode and biometrics, use biometrics with a system passcode backup.
    • If there is a system passcode but no biometrics, use the system passcode.
    • If there no system passcode force the user to log in each time.
  • In addition, if biometrics is enabled:
    • Determine if the user needs to be prompted for permission.
    • If the user needs to be prompted for permission, show a biometric prompt. This will also trigger the permissions prompt in cases where permission is required.
    • If the user allows Face ID to be used, set the vault type to DeviceSecurity.
    • If the user denies permission to use Face ID, set the vault type to InMemory.
src/composables/session-vault.ts

_112
import { useVaultFactory } from '@/composables/vault-factory';
_112
import { Session } from '@/models/session';
_112
import {
_112
BiometricPermissionState,
_112
BrowserVault,
_112
Device,
_112
DeviceSecurityType,
_112
IdentityVaultConfig,
_112
Vault,
_112
VaultType,
_112
} from '@ionic-enterprise/identity-vault';
_112
import { ref } from 'vue';
_112
_112
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_112
_112
const { createVault } = useVaultFactory();
_112
const vault: Vault | BrowserVault = createVault();
_112
const session = ref<Session | null>(null);
_112
_112
const initializeVault = async (): Promise<void> => {
_112
try {
_112
await vault.initialize({
_112
key: 'io.ionic.gettingstartediv',
_112
type: VaultType.InMemory,
_112
deviceSecurityType: DeviceSecurityType.None,
_112
lockAfterBackgrounded: 30000,
_112
});
_112
} catch (e: unknown) {
_112
await vault.clear();
_112
await updateUnlockMode('InMemory');
_112
}
_112
_112
await enhanceVault();
_112
_112
vault.onLock(() => (session.value = null));
_112
};
_112
_112
const enhanceVault = async (): Promise<void> => {
_112
if (!(await vault.isEmpty())) {
_112
return;
_112
}
_112
_112
if (await Device.isSystemPasscodeSet()) {
_112
await updateUnlockMode('BiometricsWithPasscode');
_112
} else {
_112
await updateUnlockMode('InMemory');
_112
}
_112
};
_112
_112
const storeSession = async (s: Session): Promise<void> => {
_112
vault.setValue('session', s);
_112
session.value = s;
_112
};
_112
_112
const getSession = async (): Promise<void> => {
_112
if (await vault.isEmpty()) {
_112
session.value = null;
_112
} else {
_112
session.value = await vault.getValue<Session>('session');
_112
}
_112
};
_112
_112
const clearSession = async (): Promise<void> => {
_112
await vault.clear();
_112
session.value = null;
_112
};
_112
_112
const lockSession = async (): Promise<void> => {
_112
await vault.lock();
_112
session.value = null;
_112
};
_112
_112
const unlockSession = async (): Promise<void> => {
_112
await vault.unlock();
_112
session.value = await vault.getValue<Session>('session');
_112
};
_112
_112
const sessionIsLocked = async (): Promise<boolean> => {
_112
return (
_112
vault.config?.type !== VaultType.SecureStorage &&
_112
vault.config?.type !== VaultType.InMemory &&
_112
!(await vault.isEmpty()) &&
_112
(await vault.isLocked())
_112
);
_112
};
_112
_112
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_112
const type =
_112
mode === 'BiometricsWithPasscode'
_112
? VaultType.DeviceSecurity
_112
: mode === 'InMemory'
_112
? VaultType.InMemory
_112
: VaultType.SecureStorage;
_112
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_112
const lockAfterBackgrounded = type === VaultType.InMemory ? 30000 : 2000;
_112
await vault.updateConfig({
_112
...(vault.config as IdentityVaultConfig),
_112
type,
_112
deviceSecurityType,
_112
lockAfterBackgrounded,
_112
});
_112
};
_112
_112
export const useSessionVault = (): any => ({
_112
clearSession,
_112
getSession,
_112
initializeVault,
_112
lockSession,
_112
session,
_112
storeSession,
_112
updateUnlockMode,
_112
});

Add BiometricPermissionState to the items being imported from the Identity Vault package.

src/composables/session-vault.ts

_122
import { useVaultFactory } from '@/composables/vault-factory';
_122
import { Session } from '@/models/session';
_122
import {
_122
BiometricPermissionState,
_122
BrowserVault,
_122
Device,
_122
DeviceSecurityType,
_122
IdentityVaultConfig,
_122
Vault,
_122
VaultType,
_122
} from '@ionic-enterprise/identity-vault';
_122
import { ref } from 'vue';
_122
_122
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_122
_122
const { createVault } = useVaultFactory();
_122
const vault: Vault | BrowserVault = createVault();
_122
const session = ref<Session | null>(null);
_122
_122
const initializeVault = async (): Promise<void> => {
_122
try {
_122
await vault.initialize({
_122
key: 'io.ionic.gettingstartediv',
_122
type: VaultType.InMemory,
_122
deviceSecurityType: DeviceSecurityType.None,
_122
lockAfterBackgrounded: 30000,
_122
});
_122
} catch (e: unknown) {
_122
await vault.clear();
_122
await updateUnlockMode('InMemory');
_122
}
_122
_122
await enhanceVault();
_122
_122
vault.onLock(() => (session.value = null));
_122
};
_122
_122
const enhanceVault = async (): Promise<void> => {
_122
if (!(await vault.isEmpty())) {
_122
return;
_122
}
_122
_122
if (await Device.isSystemPasscodeSet()) {
_122
await updateUnlockMode('BiometricsWithPasscode');
_122
} else {
_122
await updateUnlockMode('InMemory');
_122
}
_122
};
_122
_122
const storeSession = async (s: Session): Promise<void> => {
_122
vault.setValue('session', s);
_122
session.value = s;
_122
};
_122
_122
const getSession = async (): Promise<void> => {
_122
if (await vault.isEmpty()) {
_122
session.value = null;
_122
} else {
_122
session.value = await vault.getValue<Session>('session');
_122
}
_122
};
_122
_122
const clearSession = async (): Promise<void> => {
_122
await vault.clear();
_122
session.value = null;
_122
};
_122
_122
const lockSession = async (): Promise<void> => {
_122
await vault.lock();
_122
session.value = null;
_122
};
_122
_122
const unlockSession = async (): Promise<void> => {
_122
await vault.unlock();
_122
session.value = await vault.getValue<Session>('session');
_122
};
_122
_122
const sessionIsLocked = async (): Promise<boolean> => {
_122
return (
_122
vault.config?.type !== VaultType.SecureStorage &&
_122
vault.config?.type !== VaultType.InMemory &&
_122
!(await vault.isEmpty()) &&
_122
(await vault.isLocked())
_122
);
_122
};
_122
_122
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_122
const type =
_122
mode === 'BiometricsWithPasscode'
_122
? VaultType.DeviceSecurity
_122
: mode === 'InMemory'
_122
? VaultType.InMemory
_122
: VaultType.SecureStorage;
_122
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_122
const lockAfterBackgrounded = type === VaultType.InMemory ? 30000 : 2000;
_122
await vault.updateConfig({
_122
...(vault.config as IdentityVaultConfig),
_122
type,
_122
deviceSecurityType,
_122
lockAfterBackgrounded,
_122
});
_122
};
_122
_122
const provisionBiometrics = async (): Promise<void> => {
_122
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_122
try {
_122
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_122
} catch (error) {
_122
null;
_122
}
_122
}
_122
};
_122
_122
export const useSessionVault = (): any => ({
_122
clearSession,
_122
getSession,
_122
initializeVault,
_122
lockSession,
_122
session,
_122
storeSession,
_122
updateUnlockMode,
_122
});

Add a provisionBiometrics() function but do not export it. Display the biometric prompt if permissions need to be prompted.

src/composables/session-vault.ts

_125
import { useVaultFactory } from '@/composables/vault-factory';
_125
import { Session } from '@/models/session';
_125
import {
_125
BiometricPermissionState,
_125
BrowserVault,
_125
Device,
_125
DeviceSecurityType,
_125
IdentityVaultConfig,
_125
Vault,
_125
VaultType,
_125
} from '@ionic-enterprise/identity-vault';
_125
import { ref } from 'vue';
_125
_125
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_125
_125
const { createVault } = useVaultFactory();
_125
const vault: Vault | BrowserVault = createVault();
_125
const session = ref<Session | null>(null);
_125
_125
const initializeVault = async (): Promise<void> => {
_125
try {
_125
await vault.initialize({
_125
key: 'io.ionic.gettingstartediv',
_125
type: VaultType.InMemory,
_125
deviceSecurityType: DeviceSecurityType.None,
_125
lockAfterBackgrounded: 30000,
_125
});
_125
} catch (e: unknown) {
_125
await vault.clear();
_125
await updateUnlockMode('InMemory');
_125
}
_125
_125
await enhanceVault();
_125
_125
vault.onLock(() => (session.value = null));
_125
};
_125
_125
const enhanceVault = async (): Promise<void> => {
_125
if (!(await vault.isEmpty())) {
_125
return;
_125
}
_125
_125
if (await Device.isSystemPasscodeSet()) {
_125
await updateUnlockMode('BiometricsWithPasscode');
_125
} else {
_125
await updateUnlockMode('InMemory');
_125
}
_125
};
_125
_125
const storeSession = async (s: Session): Promise<void> => {
_125
vault.setValue('session', s);
_125
session.value = s;
_125
};
_125
_125
const getSession = async (): Promise<void> => {
_125
if (await vault.isEmpty()) {
_125
session.value = null;
_125
} else {
_125
session.value = await vault.getValue<Session>('session');
_125
}
_125
};
_125
_125
const clearSession = async (): Promise<void> => {
_125
await vault.clear();
_125
session.value = null;
_125
};
_125
_125
const lockSession = async (): Promise<void> => {
_125
await vault.lock();
_125
session.value = null;
_125
};
_125
_125
const unlockSession = async (): Promise<void> => {
_125
await vault.unlock();
_125
session.value = await vault.getValue<Session>('session');
_125
};
_125
_125
const sessionIsLocked = async (): Promise<boolean> => {
_125
return (
_125
vault.config?.type !== VaultType.SecureStorage &&
_125
vault.config?.type !== VaultType.InMemory &&
_125
!(await vault.isEmpty()) &&
_125
(await vault.isLocked())
_125
);
_125
};
_125
_125
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_125
const type = getVaultType(mode);
_125
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_125
const lockAfterBackgrounded = type === VaultType.InMemory ? 30000 : 2000;
_125
await vault.updateConfig({
_125
...(vault.config as IdentityVaultConfig),
_125
type,
_125
deviceSecurityType,
_125
lockAfterBackgrounded,
_125
});
_125
};
_125
_125
const getVaultType = async (mode: UnlockMode): Promise<VaultType> => {
_125
return mode === 'BiometricsWithPasscode'
_125
? VaultType.DeviceSecurity
_125
: mode === 'InMemory'
_125
? VaultType.InMemory
_125
: VaultType.SecureStorage;
_125
};
_125
_125
const provisionBiometrics = async (): Promise<void> => {
_125
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_125
try {
_125
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_125
} catch (error) {
_125
null;
_125
}
_125
}
_125
};
_125
_125
export const useSessionVault = (): any => ({
_125
clearSession,
_125
getSession,
_125
initializeVault,
_125
lockSession,
_125
session,
_125
storeSession,
_125
updateUnlockMode,
_125
});

Determining the proper vault type is going to get more complex, so let's start by abstracting that logic into its own method.

src/composables/session-vault.ts

_125
import { useVaultFactory } from '@/composables/vault-factory';
_125
import { Session } from '@/models/session';
_125
import {
_125
BiometricPermissionState,
_125
BrowserVault,
_125
Device,
_125
DeviceSecurityType,
_125
IdentityVaultConfig,
_125
Vault,
_125
VaultType,
_125
} from '@ionic-enterprise/identity-vault';
_125
import { ref } from 'vue';
_125
_125
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_125
_125
const { createVault } = useVaultFactory();
_125
const vault: Vault | BrowserVault = createVault();
_125
const session = ref<Session | null>(null);
_125
_125
const initializeVault = async (): Promise<void> => {
_125
try {
_125
await vault.initialize({
_125
key: 'io.ionic.gettingstartediv',
_125
type: VaultType.InMemory,
_125
deviceSecurityType: DeviceSecurityType.None,
_125
lockAfterBackgrounded: 30000,
_125
});
_125
} catch (e: unknown) {
_125
await vault.clear();
_125
await updateUnlockMode('InMemory');
_125
}
_125
_125
await enhanceVault();
_125
_125
vault.onLock(() => (session.value = null));
_125
};
_125
_125
const enhanceVault = async (): Promise<void> => {
_125
if (!(await vault.isEmpty())) {
_125
return;
_125
}
_125
_125
if (await Device.isSystemPasscodeSet()) {
_125
await updateUnlockMode('BiometricsWithPasscode');
_125
} else {
_125
await updateUnlockMode('InMemory');
_125
}
_125
};
_125
_125
const storeSession = async (s: Session): Promise<void> => {
_125
vault.setValue('session', s);
_125
session.value = s;
_125
};
_125
_125
const getSession = async (): Promise<void> => {
_125
if (await vault.isEmpty()) {
_125
session.value = null;
_125
} else {
_125
session.value = await vault.getValue<Session>('session');
_125
}
_125
};
_125
_125
const clearSession = async (): Promise<void> => {
_125
await vault.clear();
_125
session.value = null;
_125
};
_125
_125
const lockSession = async (): Promise<void> => {
_125
await vault.lock();
_125
session.value = null;
_125
};
_125
_125
const unlockSession = async (): Promise<void> => {
_125
await vault.unlock();
_125
session.value = await vault.getValue<Session>('session');
_125
};
_125
_125
const sessionIsLocked = async (): Promise<boolean> => {
_125
return (
_125
vault.config?.type !== VaultType.SecureStorage &&
_125
vault.config?.type !== VaultType.InMemory &&
_125
!(await vault.isEmpty()) &&
_125
(await vault.isLocked())
_125
);
_125
};
_125
_125
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_125
const type = getVaultType(mode);
_125
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_125
const lockAfterBackgrounded = type === VaultType.InMemory ? 30000 : 2000;
_125
await vault.updateConfig({
_125
...(vault.config as IdentityVaultConfig),
_125
type,
_125
deviceSecurityType,
_125
lockAfterBackgrounded,
_125
});
_125
};
_125
_125
const getVaultType = async (mode: UnlockMode): Promise<VaultType> => {
_125
if (mode === 'BiometricsWithPasscode') {
_125
return VaultType.DeviceSecurity;
_125
}
_125
_125
return mode === 'InMemory' ? VaultType.InMemory : VaultType.SecureStorage;
_125
};
_125
_125
const provisionBiometrics = async (): Promise<void> => {
_125
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_125
try {
_125
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_125
} catch (error) {
_125
null;
_125
}
_125
}
_125
};
_125
_125
export const useSessionVault = (): any => ({
_125
clearSession,
_125
getSession,
_125
initializeVault,
_125
lockSession,
_125
session,
_125
storeSession,
_125
updateUnlockMode,
_125
});

Biometrics will be special, so let's give it its own section.

src/composables/session-vault.ts

_126
import { useVaultFactory } from '@/composables/vault-factory';
_126
import { Session } from '@/models/session';
_126
import {
_126
BiometricPermissionState,
_126
BrowserVault,
_126
Device,
_126
DeviceSecurityType,
_126
IdentityVaultConfig,
_126
Vault,
_126
VaultType,
_126
} from '@ionic-enterprise/identity-vault';
_126
import { ref } from 'vue';
_126
_126
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_126
_126
const { createVault } = useVaultFactory();
_126
const vault: Vault | BrowserVault = createVault();
_126
const session = ref<Session | null>(null);
_126
_126
const initializeVault = async (): Promise<void> => {
_126
try {
_126
await vault.initialize({
_126
key: 'io.ionic.gettingstartediv',
_126
type: VaultType.InMemory,
_126
deviceSecurityType: DeviceSecurityType.None,
_126
lockAfterBackgrounded: 30000,
_126
});
_126
} catch (e: unknown) {
_126
await vault.clear();
_126
await updateUnlockMode('InMemory');
_126
}
_126
_126
await enhanceVault();
_126
_126
vault.onLock(() => (session.value = null));
_126
};
_126
_126
const enhanceVault = async (): Promise<void> => {
_126
if (!(await vault.isEmpty())) {
_126
return;
_126
}
_126
_126
if (await Device.isSystemPasscodeSet()) {
_126
await updateUnlockMode('BiometricsWithPasscode');
_126
} else {
_126
await updateUnlockMode('InMemory');
_126
}
_126
};
_126
_126
const storeSession = async (s: Session): Promise<void> => {
_126
vault.setValue('session', s);
_126
session.value = s;
_126
};
_126
_126
const getSession = async (): Promise<void> => {
_126
if (await vault.isEmpty()) {
_126
session.value = null;
_126
} else {
_126
session.value = await vault.getValue<Session>('session');
_126
}
_126
};
_126
_126
const clearSession = async (): Promise<void> => {
_126
await vault.clear();
_126
session.value = null;
_126
};
_126
_126
const lockSession = async (): Promise<void> => {
_126
await vault.lock();
_126
session.value = null;
_126
};
_126
_126
const unlockSession = async (): Promise<void> => {
_126
await vault.unlock();
_126
session.value = await vault.getValue<Session>('session');
_126
};
_126
_126
const sessionIsLocked = async (): Promise<boolean> => {
_126
return (
_126
vault.config?.type !== VaultType.SecureStorage &&
_126
vault.config?.type !== VaultType.InMemory &&
_126
!(await vault.isEmpty()) &&
_126
(await vault.isLocked())
_126
);
_126
};
_126
_126
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_126
const type = getVaultType(mode);
_126
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_126
const lockAfterBackgrounded = type === VaultType.InMemory ? 30000 : 2000;
_126
await vault.updateConfig({
_126
...(vault.config as IdentityVaultConfig),
_126
type,
_126
deviceSecurityType,
_126
lockAfterBackgrounded,
_126
});
_126
};
_126
_126
const getVaultType = async (mode: UnlockMode): Promise<VaultType> => {
_126
if (mode === 'BiometricsWithPasscode') {
_126
await provisionBiometrics();
_126
return VaultType.DeviceSecurity;
_126
}
_126
_126
return mode === 'InMemory' ? VaultType.InMemory : VaultType.SecureStorage;
_126
};
_126
_126
const provisionBiometrics = async (): Promise<void> => {
_126
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_126
try {
_126
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_126
} catch (error) {
_126
null;
_126
}
_126
}
_126
};
_126
_126
export const useSessionVault = (): any => ({
_126
clearSession,
_126
getSession,
_126
initializeVault,
_126
lockSession,
_126
session,
_126
storeSession,
_126
updateUnlockMode,
_126
});

Provision the Biometrics.

src/composables/session-vault.ts

_129
import { useVaultFactory } from '@/composables/vault-factory';
_129
import { Session } from '@/models/session';
_129
import {
_129
BiometricPermissionState,
_129
BrowserVault,
_129
Device,
_129
DeviceSecurityType,
_129
IdentityVaultConfig,
_129
Vault,
_129
VaultType,
_129
} from '@ionic-enterprise/identity-vault';
_129
import { ref } from 'vue';
_129
_129
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_129
_129
const { createVault } = useVaultFactory();
_129
const vault: Vault | BrowserVault = createVault();
_129
const session = ref<Session | null>(null);
_129
_129
const initializeVault = async (): Promise<void> => {
_129
try {
_129
await vault.initialize({
_129
key: 'io.ionic.gettingstartediv',
_129
type: VaultType.InMemory,
_129
deviceSecurityType: DeviceSecurityType.None,
_129
lockAfterBackgrounded: 30000,
_129
});
_129
} catch (e: unknown) {
_129
await vault.clear();
_129
await updateUnlockMode('InMemory');
_129
}
_129
_129
await enhanceVault();
_129
_129
vault.onLock(() => (session.value = null));
_129
};
_129
_129
const enhanceVault = async (): Promise<void> => {
_129
if (!(await vault.isEmpty())) {
_129
return;
_129
}
_129
_129
if (await Device.isSystemPasscodeSet()) {
_129
await updateUnlockMode('BiometricsWithPasscode');
_129
} else {
_129
await updateUnlockMode('InMemory');
_129
}
_129
};
_129
_129
const storeSession = async (s: Session): Promise<void> => {
_129
vault.setValue('session', s);
_129
session.value = s;
_129
};
_129
_129
const getSession = async (): Promise<void> => {
_129
if (await vault.isEmpty()) {
_129
session.value = null;
_129
} else {
_129
session.value = await vault.getValue<Session>('session');
_129
}
_129
};
_129
_129
const clearSession = async (): Promise<void> => {
_129
await vault.clear();
_129
session.value = null;
_129
};
_129
_129
const lockSession = async (): Promise<void> => {
_129
await vault.lock();
_129
session.value = null;
_129
};
_129
_129
const unlockSession = async (): Promise<void> => {
_129
await vault.unlock();
_129
session.value = await vault.getValue<Session>('session');
_129
};
_129
_129
const sessionIsLocked = async (): Promise<boolean> => {
_129
return (
_129
vault.config?.type !== VaultType.SecureStorage &&
_129
vault.config?.type !== VaultType.InMemory &&
_129
!(await vault.isEmpty()) &&
_129
(await vault.isLocked())
_129
);
_129
};
_129
_129
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_129
const type = getVaultType(mode);
_129
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_129
const lockAfterBackgrounded = type === VaultType.InMemory ? 30000 : 2000;
_129
await vault.updateConfig({
_129
...(vault.config as IdentityVaultConfig),
_129
type,
_129
deviceSecurityType,
_129
lockAfterBackgrounded,
_129
});
_129
};
_129
_129
const getVaultType = async (mode: UnlockMode): Promise<VaultType> => {
_129
if (mode === 'BiometricsWithPasscode') {
_129
await provisionBiometrics();
_129
return (await Device.isBiometricsEnabled()) &&
_129
(await Device.isBiometricsAllowed()) !== BiometricPermissionState.Granted
_129
? VaultType.InMemory
_129
: VaultType.DeviceSecurity;
_129
}
_129
_129
return mode === 'InMemory' ? VaultType.InMemory : VaultType.SecureStorage;
_129
};
_129
_129
const provisionBiometrics = async (): Promise<void> => {
_129
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_129
try {
_129
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_129
} catch (error) {
_129
null;
_129
}
_129
}
_129
};
_129
_129
export const useSessionVault = (): any => ({
_129
clearSession,
_129
getSession,
_129
initializeVault,
_129
lockSession,
_129
session,
_129
storeSession,
_129
updateUnlockMode,
_129
});

If biometrics has been set up, return the vault type based on whether the user has granted or denied access to Face ID.

Add BiometricPermissionState to the items being imported from the Identity Vault package.

Add a provisionBiometrics() function but do not export it. Display the biometric prompt if permissions need to be prompted.

Determining the proper vault type is going to get more complex, so let's start by abstracting that logic into its own method.

Biometrics will be special, so let's give it its own section.

Provision the Biometrics.

If biometrics has been set up, return the vault type based on whether the user has granted or denied access to Face ID.

src/composables/session-vault.ts

_112
import { useVaultFactory } from '@/composables/vault-factory';
_112
import { Session } from '@/models/session';
_112
import {
_112
BiometricPermissionState,
_112
BrowserVault,
_112
Device,
_112
DeviceSecurityType,
_112
IdentityVaultConfig,
_112
Vault,
_112
VaultType,
_112
} from '@ionic-enterprise/identity-vault';
_112
import { ref } from 'vue';
_112
_112
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_112
_112
const { createVault } = useVaultFactory();
_112
const vault: Vault | BrowserVault = createVault();
_112
const session = ref<Session | null>(null);
_112
_112
const initializeVault = async (): Promise<void> => {
_112
try {
_112
await vault.initialize({
_112
key: 'io.ionic.gettingstartediv',
_112
type: VaultType.InMemory,
_112
deviceSecurityType: DeviceSecurityType.None,
_112
lockAfterBackgrounded: 30000,
_112
});
_112
} catch (e: unknown) {
_112
await vault.clear();
_112
await updateUnlockMode('InMemory');
_112
}
_112
_112
await enhanceVault();
_112
_112
vault.onLock(() => (session.value = null));
_112
};
_112
_112
const enhanceVault = async (): Promise<void> => {
_112
if (!(await vault.isEmpty())) {
_112
return;
_112
}
_112
_112
if (await Device.isSystemPasscodeSet()) {
_112
await updateUnlockMode('BiometricsWithPasscode');
_112
} else {
_112
await updateUnlockMode('InMemory');
_112
}
_112
};
_112
_112
const storeSession = async (s: Session): Promise<void> => {
_112
vault.setValue('session', s);
_112
session.value = s;
_112
};
_112
_112
const getSession = async (): Promise<void> => {
_112
if (await vault.isEmpty()) {
_112
session.value = null;
_112
} else {
_112
session.value = await vault.getValue<Session>('session');
_112
}
_112
};
_112
_112
const clearSession = async (): Promise<void> => {
_112
await vault.clear();
_112
session.value = null;
_112
};
_112
_112
const lockSession = async (): Promise<void> => {
_112
await vault.lock();
_112
session.value = null;
_112
};
_112
_112
const unlockSession = async (): Promise<void> => {
_112
await vault.unlock();
_112
session.value = await vault.getValue<Session>('session');
_112
};
_112
_112
const sessionIsLocked = async (): Promise<boolean> => {
_112
return (
_112
vault.config?.type !== VaultType.SecureStorage &&
_112
vault.config?.type !== VaultType.InMemory &&
_112
!(await vault.isEmpty()) &&
_112
(await vault.isLocked())
_112
);
_112
};
_112
_112
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_112
const type =
_112
mode === 'BiometricsWithPasscode'
_112
? VaultType.DeviceSecurity
_112
: mode === 'InMemory'
_112
? VaultType.InMemory
_112
: VaultType.SecureStorage;
_112
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_112
const lockAfterBackgrounded = type === VaultType.InMemory ? 30000 : 2000;
_112
await vault.updateConfig({
_112
...(vault.config as IdentityVaultConfig),
_112
type,
_112
deviceSecurityType,
_112
lockAfterBackgrounded,
_112
});
_112
};
_112
_112
export const useSessionVault = (): any => ({
_112
clearSession,
_112
getSession,
_112
initializeVault,
_112
lockSession,
_112
session,
_112
storeSession,
_112
updateUnlockMode,
_112
});

Perform a fresh install on several different devices. On iOS devices that use Face ID, the app should ask permission to use the Face ID upon the first start after application install. If you allow the use of Face ID, the application will use Biometrics with a System Passcode backup. If you disallow the use of Face ID, the application will use an "In Memory" vault.

On all other devices, you should not receive any permission requests and application will use Biometrics with a System Passcode backup by default.

Notice that the permission request comes during application startup. This may be jarring to some users. For this application it may be better to ask for the Face ID permission right after the user logs in. Since the provisioning is tied to the enhancement of the vault to use biometrics, this means delaying the enhancement of the vault until after login.

src/composables/session-vault.ts
src/views/LoginPage.vue

_130
import { useVaultFactory } from '@/composables/vault-factory';
_130
import { Session } from '@/models/session';
_130
import {
_130
BiometricPermissionState,
_130
BrowserVault,
_130
Device,
_130
DeviceSecurityType,
_130
IdentityVaultConfig,
_130
Vault,
_130
VaultType,
_130
} from '@ionic-enterprise/identity-vault';
_130
import { ref } from 'vue';
_130
_130
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_130
_130
const { createVault } = useVaultFactory();
_130
const vault: Vault | BrowserVault = createVault();
_130
const session = ref<Session | null>(null);
_130
_130
const initializeVault = async (): Promise<void> => {
_130
try {
_130
await vault.initialize({
_130
key: 'io.ionic.gettingstartediv',
_130
type: VaultType.InMemory,
_130
deviceSecurityType: DeviceSecurityType.None,
_130
lockAfterBackgrounded: 30000,
_130
});
_130
} catch (e: unknown) {
_130
await vault.clear();
_130
await updateUnlockMode('InMemory');
_130
}
_130
_130
await enhanceVault();
_130
_130
vault.onLock(() => (session.value = null));
_130
};
_130
_130
const enhanceVault = async (): Promise<void> => {
_130
if (!(await vault.isEmpty())) {
_130
return;
_130
}
_130
_130
if (await Device.isSystemPasscodeSet()) {
_130
await updateUnlockMode('BiometricsWithPasscode');
_130
} else {
_130
await updateUnlockMode('InMemory');
_130
}
_130
};
_130
_130
const storeSession = async (s: Session): Promise<void> => {
_130
vault.setValue('session', s);
_130
session.value = s;
_130
};
_130
_130
const getSession = async (): Promise<void> => {
_130
if (await vault.isEmpty()) {
_130
session.value = null;
_130
} else {
_130
session.value = await vault.getValue<Session>('session');
_130
}
_130
};
_130
_130
const clearSession = async (): Promise<void> => {
_130
await vault.clear();
_130
session.value = null;
_130
};
_130
_130
const lockSession = async (): Promise<void> => {
_130
await vault.lock();
_130
session.value = null;
_130
};
_130
_130
const unlockSession = async (): Promise<void> => {
_130
await vault.unlock();
_130
session.value = await vault.getValue<Session>('session');
_130
};
_130
_130
const sessionIsLocked = async (): Promise<boolean> => {
_130
return (
_130
vault.config?.type !== VaultType.SecureStorage &&
_130
vault.config?.type !== VaultType.InMemory &&
_130
!(await vault.isEmpty()) &&
_130
(await vault.isLocked())
_130
);
_130
};
_130
_130
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_130
const type = getVaultType(mode);
_130
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_130
const lockAfterBackgrounded = type === VaultType.InMemory ? 30000 : 2000;
_130
await vault.updateConfig({
_130
...(vault.config as IdentityVaultConfig),
_130
type,
_130
deviceSecurityType,
_130
lockAfterBackgrounded,
_130
});
_130
};
_130
_130
const getVaultType = async (mode: UnlockMode): Promise<VaultType> => {
_130
if (mode === 'BiometricsWithPasscode') {
_130
await provisionBiometrics();
_130
return (await Device.isBiometricsEnabled()) &&
_130
(await Device.isBiometricsAllowed()) !== BiometricPermissionState.Granted
_130
? VaultType.InMemory
_130
: VaultType.DeviceSecurity;
_130
}
_130
_130
return mode === 'InMemory' ? VaultType.InMemory : VaultType.SecureStorage;
_130
};
_130
_130
const provisionBiometrics = async (): Promise<void> => {
_130
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_130
try {
_130
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_130
} catch (error) {
_130
null;
_130
}
_130
}
_130
};
_130
_130
export const useSessionVault = (): any => ({
_130
clearSession,
_130
enhanceVault,
_130
getSession,
_130
initializeVault,
_130
lockSession,
_130
session,
_130
storeSession,
_130
updateUnlockMode,
_130
});

Export the enhanceVault() function.

src/composables/session-vault.ts
src/views/LoginPage.vue

_130
import { useVaultFactory } from '@/composables/vault-factory';
_130
import { Session } from '@/models/session';
_130
import {
_130
BiometricPermissionState,
_130
BrowserVault,
_130
Device,
_130
DeviceSecurityType,
_130
IdentityVaultConfig,
_130
Vault,
_130
VaultType,
_130
} from '@ionic-enterprise/identity-vault';
_130
import { ref } from 'vue';
_130
_130
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_130
_130
const { createVault } = useVaultFactory();
_130
const vault: Vault | BrowserVault = createVault();
_130
const session = ref<Session | null>(null);
_130
_130
const initializeVault = async (): Promise<void> => {
_130
try {
_130
await vault.initialize({
_130
key: 'io.ionic.gettingstartediv',
_130
type: VaultType.InMemory,
_130
deviceSecurityType: DeviceSecurityType.None,
_130
lockAfterBackgrounded: 30000,
_130
});
_130
} catch (e: unknown) {
_130
await vault.clear();
_130
await updateUnlockMode('InMemory');
_130
}
_130
_130
await enhanceVault();
_130
_130
vault.onLock(() => (session.value = null));
_130
};
_130
_130
const enhanceVault = async (): Promise<void> => {
_130
if (!(await vault.isEmpty())) {
_130
return;
_130
}
_130
_130
if (await Device.isSystemPasscodeSet()) {
_130
await updateUnlockMode('BiometricsWithPasscode');
_130
} else {
_130
await updateUnlockMode('InMemory');
_130
}
_130
};
_130
_130
const storeSession = async (s: Session): Promise<void> => {
_130
vault.setValue('session', s);
_130
session.value = s;
_130
};
_130
_130
const getSession = async (): Promise<void> => {
_130
if (await vault.isEmpty()) {
_130
session.value = null;
_130
} else {
_130
session.value = await vault.getValue<Session>('session');
_130
}
_130
};
_130
_130
const clearSession = async (): Promise<void> => {
_130
await vault.clear();
_130
session.value = null;
_130
};
_130
_130
const lockSession = async (): Promise<void> => {
_130
await vault.lock();
_130
session.value = null;
_130
};
_130
_130
const unlockSession = async (): Promise<void> => {
_130
await vault.unlock();
_130
session.value = await vault.getValue<Session>('session');
_130
};
_130
_130
const sessionIsLocked = async (): Promise<boolean> => {
_130
return (
_130
vault.config?.type !== VaultType.SecureStorage &&
_130
vault.config?.type !== VaultType.InMemory &&
_130
!(await vault.isEmpty()) &&
_130
(await vault.isLocked())
_130
);
_130
};
_130
_130
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_130
const type = getVaultType(mode);
_130
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_130
const lockAfterBackgrounded = type === VaultType.InMemory ? 30000 : 2000;
_130
await vault.updateConfig({
_130
...(vault.config as IdentityVaultConfig),
_130
type,
_130
deviceSecurityType,
_130
lockAfterBackgrounded,
_130
});
_130
};
_130
_130
const getVaultType = async (mode: UnlockMode): Promise<VaultType> => {
_130
if (mode === 'BiometricsWithPasscode') {
_130
await provisionBiometrics();
_130
return (await Device.isBiometricsEnabled()) &&
_130
(await Device.isBiometricsAllowed()) !== BiometricPermissionState.Granted
_130
? VaultType.InMemory
_130
: VaultType.DeviceSecurity;
_130
}
_130
_130
return mode === 'InMemory' ? VaultType.InMemory : VaultType.SecureStorage;
_130
};
_130
_130
const provisionBiometrics = async (): Promise<void> => {
_130
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_130
try {
_130
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_130
} catch (error) {
_130
null;
_130
}
_130
}
_130
};
_130
_130
export const useSessionVault = (): any => ({
_130
clearSession,
_130
enhanceVault,
_130
getSession,
_130
initializeVault,
_130
lockSession,
_130
session,
_130
storeSession,
_130
updateUnlockMode,
_130
});

enhanceVault() currently does not enhance a vault that is in use.

src/composables/session-vault.ts
src/views/LoginPage.vue

_126
import { useVaultFactory } from '@/composables/vault-factory';
_126
import { Session } from '@/models/session';
_126
import {
_126
BiometricPermissionState,
_126
BrowserVault,
_126
Device,
_126
DeviceSecurityType,
_126
IdentityVaultConfig,
_126
Vault,
_126
VaultType,
_126
} from '@ionic-enterprise/identity-vault';
_126
import { ref } from 'vue';
_126
_126
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_126
_126
const { createVault } = useVaultFactory();
_126
const vault: Vault | BrowserVault = createVault();
_126
const session = ref<Session | null>(null);
_126
_126
const initializeVault = async (): Promise<void> => {
_126
try {
_126
await vault.initialize({
_126
key: 'io.ionic.gettingstartediv',
_126
type: VaultType.InMemory,
_126
deviceSecurityType: DeviceSecurityType.None,
_126
lockAfterBackgrounded: 30000,
_126
});
_126
} catch (e: unknown) {
_126
await vault.clear();
_126
await updateUnlockMode('InMemory');
_126
}
_126
_126
await enhanceVault();
_126
_126
vault.onLock(() => (session.value = null));
_126
};
_126
_126
const enhanceVault = async (): Promise<void> => {
_126
if (await Device.isSystemPasscodeSet()) {
_126
await updateUnlockMode('BiometricsWithPasscode');
_126
} else {
_126
await updateUnlockMode('InMemory');
_126
}
_126
};
_126
_126
const storeSession = async (s: Session): Promise<void> => {
_126
vault.setValue('session', s);
_126
session.value = s;
_126
};
_126
_126
const getSession = async (): Promise<void> => {
_126
if (await vault.isEmpty()) {
_126
session.value = null;
_126
} else {
_126
session.value = await vault.getValue<Session>('session');
_126
}
_126
};
_126
_126
const clearSession = async (): Promise<void> => {
_126
await vault.clear();
_126
session.value = null;
_126
};
_126
_126
const lockSession = async (): Promise<void> => {
_126
await vault.lock();
_126
session.value = null;
_126
};
_126
_126
const unlockSession = async (): Promise<void> => {
_126
await vault.unlock();
_126
session.value = await vault.getValue<Session>('session');
_126
};
_126
_126
const sessionIsLocked = async (): Promise<boolean> => {
_126
return (
_126
vault.config?.type !== VaultType.SecureStorage &&
_126
vault.config?.type !== VaultType.InMemory &&
_126
!(await vault.isEmpty()) &&
_126
(await vault.isLocked())
_126
);
_126
};
_126
_126
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_126
const type = getVaultType(mode);
_126
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_126
const lockAfterBackgrounded = type === VaultType.InMemory ? 30000 : 2000;
_126
await vault.updateConfig({
_126
...(vault.config as IdentityVaultConfig),
_126
type,
_126
deviceSecurityType,
_126
lockAfterBackgrounded,
_126
});
_126
};
_126
_126
const getVaultType = async (mode: UnlockMode): Promise<VaultType> => {
_126
if (mode === 'BiometricsWithPasscode') {
_126
await provisionBiometrics();
_126
return (await Device.isBiometricsEnabled()) &&
_126
(await Device.isBiometricsAllowed()) !== BiometricPermissionState.Granted
_126
? VaultType.InMemory
_126
: VaultType.DeviceSecurity;
_126
}
_126
_126
return mode === 'InMemory' ? VaultType.InMemory : VaultType.SecureStorage;
_126
};
_126
_126
const provisionBiometrics = async (): Promise<void> => {
_126
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_126
try {
_126
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_126
} catch (error) {
_126
null;
_126
}
_126
}
_126
};
_126
_126
export const useSessionVault = (): any => ({
_126
clearSession,
_126
enhanceVault,
_126
getSession,
_126
initializeVault,
_126
lockSession,
_126
session,
_126
storeSession,
_126
updateUnlockMode,
_126
});

Preventing this is no longer necessary. Remove the isEmpty() check leaving the rest of the code in place.

src/composables/session-vault.ts
src/views/LoginPage.vue

_126
import { useVaultFactory } from '@/composables/vault-factory';
_126
import { Session } from '@/models/session';
_126
import {
_126
BiometricPermissionState,
_126
BrowserVault,
_126
Device,
_126
DeviceSecurityType,
_126
IdentityVaultConfig,
_126
Vault,
_126
VaultType,
_126
} from '@ionic-enterprise/identity-vault';
_126
import { ref } from 'vue';
_126
_126
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_126
_126
const { createVault } = useVaultFactory();
_126
const vault: Vault | BrowserVault = createVault();
_126
const session = ref<Session | null>(null);
_126
_126
const initializeVault = async (): Promise<void> => {
_126
try {
_126
await vault.initialize({
_126
key: 'io.ionic.gettingstartediv',
_126
type: VaultType.InMemory,
_126
deviceSecurityType: DeviceSecurityType.None,
_126
lockAfterBackgrounded: 30000,
_126
});
_126
} catch (e: unknown) {
_126
await vault.clear();
_126
await updateUnlockMode('InMemory');
_126
}
_126
_126
await enhanceVault();
_126
_126
vault.onLock(() => (session.value = null));
_126
};
_126
_126
const enhanceVault = async (): Promise<void> => {
_126
if (await Device.isSystemPasscodeSet()) {
_126
await updateUnlockMode('BiometricsWithPasscode');
_126
} else {
_126
await updateUnlockMode('InMemory');
_126
}
_126
};
_126
_126
const storeSession = async (s: Session): Promise<void> => {
_126
vault.setValue('session', s);
_126
session.value = s;
_126
};
_126
_126
const getSession = async (): Promise<void> => {
_126
if (await vault.isEmpty()) {
_126
session.value = null;
_126
} else {
_126
session.value = await vault.getValue<Session>('session');
_126
}
_126
};
_126
_126
const clearSession = async (): Promise<void> => {
_126
await vault.clear();
_126
session.value = null;
_126
};
_126
_126
const lockSession = async (): Promise<void> => {
_126
await vault.lock();
_126
session.value = null;
_126
};
_126
_126
const unlockSession = async (): Promise<void> => {
_126
await vault.unlock();
_126
session.value = await vault.getValue<Session>('session');
_126
};
_126
_126
const sessionIsLocked = async (): Promise<boolean> => {
_126
return (
_126
vault.config?.type !== VaultType.SecureStorage &&
_126
vault.config?.type !== VaultType.InMemory &&
_126
!(await vault.isEmpty()) &&
_126
(await vault.isLocked())
_126
);
_126
};
_126
_126
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_126
const type = getVaultType(mode);
_126
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_126
const lockAfterBackgrounded = type === VaultType.InMemory ? 30000 : 2000;
_126
await vault.updateConfig({
_126
...(vault.config as IdentityVaultConfig),
_126
type,
_126
deviceSecurityType,
_126
lockAfterBackgrounded,
_126
});
_126
};
_126
_126
const getVaultType = async (mode: UnlockMode): Promise<VaultType> => {
_126
if (mode === 'BiometricsWithPasscode') {
_126
await provisionBiometrics();
_126
return (await Device.isBiometricsEnabled()) &&
_126
(await Device.isBiometricsAllowed()) !== BiometricPermissionState.Granted
_126
? VaultType.InMemory
_126
: VaultType.DeviceSecurity;
_126
}
_126
_126
return mode === 'InMemory' ? VaultType.InMemory : VaultType.SecureStorage;
_126
};
_126
_126
const provisionBiometrics = async (): Promise<void> => {
_126
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_126
try {
_126
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_126
} catch (error) {
_126
null;
_126
}
_126
}
_126
};
_126
_126
export const useSessionVault = (): any => ({
_126
clearSession,
_126
enhanceVault,
_126
getSession,
_126
initializeVault,
_126
lockSession,
_126
session,
_126
storeSession,
_126
updateUnlockMode,
_126
});

enhanceVault() is currently called from the initialize() function.

src/composables/session-vault.ts
src/views/LoginPage.vue

_124
import { useVaultFactory } from '@/composables/vault-factory';
_124
import { Session } from '@/models/session';
_124
import {
_124
BiometricPermissionState,
_124
BrowserVault,
_124
Device,
_124
DeviceSecurityType,
_124
IdentityVaultConfig,
_124
Vault,
_124
VaultType,
_124
} from '@ionic-enterprise/identity-vault';
_124
import { ref } from 'vue';
_124
_124
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_124
_124
const { createVault } = useVaultFactory();
_124
const vault: Vault | BrowserVault = createVault();
_124
const session = ref<Session | null>(null);
_124
_124
const initializeVault = async (): Promise<void> => {
_124
try {
_124
await vault.initialize({
_124
key: 'io.ionic.gettingstartediv',
_124
type: VaultType.InMemory,
_124
deviceSecurityType: DeviceSecurityType.None,
_124
lockAfterBackgrounded: 30000,
_124
});
_124
} catch (e: unknown) {
_124
await vault.clear();
_124
await updateUnlockMode('InMemory');
_124
}
_124
_124
vault.onLock(() => (session.value = null));
_124
};
_124
_124
const enhanceVault = async (): Promise<void> => {
_124
if (await Device.isSystemPasscodeSet()) {
_124
await updateUnlockMode('BiometricsWithPasscode');
_124
} else {
_124
await updateUnlockMode('InMemory');
_124
}
_124
};
_124
_124
const storeSession = async (s: Session): Promise<void> => {
_124
vault.setValue('session', s);
_124
session.value = s;
_124
};
_124
_124
const getSession = async (): Promise<void> => {
_124
if (await vault.isEmpty()) {
_124
session.value = null;
_124
} else {
_124
session.value = await vault.getValue<Session>('session');
_124
}
_124
};
_124
_124
const clearSession = async (): Promise<void> => {
_124
await vault.clear();
_124
session.value = null;
_124
};
_124
_124
const lockSession = async (): Promise<void> => {
_124
await vault.lock();
_124
session.value = null;
_124
};
_124
_124
const unlockSession = async (): Promise<void> => {
_124
await vault.unlock();
_124
session.value = await vault.getValue<Session>('session');
_124
};
_124
_124
const sessionIsLocked = async (): Promise<boolean> => {
_124
return (
_124
vault.config?.type !== VaultType.SecureStorage &&
_124
vault.config?.type !== VaultType.InMemory &&
_124
!(await vault.isEmpty()) &&
_124
(await vault.isLocked())
_124
);
_124
};
_124
_124
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_124
const type = getVaultType(mode);
_124
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_124
const lockAfterBackgrounded = type === VaultType.InMemory ? 30000 : 2000;
_124
await vault.updateConfig({
_124
...(vault.config as IdentityVaultConfig),
_124
type,
_124
deviceSecurityType,
_124
lockAfterBackgrounded,
_124
});
_124
};
_124
_124
const getVaultType = async (mode: UnlockMode): Promise<VaultType> => {
_124
if (mode === 'BiometricsWithPasscode') {
_124
await provisionBiometrics();
_124
return (await Device.isBiometricsEnabled()) &&
_124
(await Device.isBiometricsAllowed()) !== BiometricPermissionState.Granted
_124
? VaultType.InMemory
_124
: VaultType.DeviceSecurity;
_124
}
_124
_124
return mode === 'InMemory' ? VaultType.InMemory : VaultType.SecureStorage;
_124
};
_124
_124
const provisionBiometrics = async (): Promise<void> => {
_124
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_124
try {
_124
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_124
} catch (error) {
_124
null;
_124
}
_124
}
_124
};
_124
_124
export const useSessionVault = (): any => ({
_124
clearSession,
_124
enhanceVault,
_124
getSession,
_124
initializeVault,
_124
lockSession,
_124
session,
_124
storeSession,
_124
updateUnlockMode,
_124
});

Remove the call leaving the rest of initialize() in place.

src/composables/session-vault.ts
src/views/LoginPage.vue

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

In the LoginPage, the login handler currently logs the user in and navigates to the main page.

src/composables/session-vault.ts
src/views/LoginPage.vue

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

Enhance the vault as part of a successful login.

Export the enhanceVault() function.

enhanceVault() currently does not enhance a vault that is in use.

Preventing this is no longer necessary. Remove the isEmpty() check leaving the rest of the code in place.

enhanceVault() is currently called from the initialize() function.

Remove the call leaving the rest of initialize() in place.

In the LoginPage, the login handler currently logs the user in and navigates to the main page.

Enhance the vault as part of a successful login.

src/composables/session-vault.ts
src/views/LoginPage.vue

_130
import { useVaultFactory } from '@/composables/vault-factory';
_130
import { Session } from '@/models/session';
_130
import {
_130
BiometricPermissionState,
_130
BrowserVault,
_130
Device,
_130
DeviceSecurityType,
_130
IdentityVaultConfig,
_130
Vault,
_130
VaultType,
_130
} from '@ionic-enterprise/identity-vault';
_130
import { ref } from 'vue';
_130
_130
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_130
_130
const { createVault } = useVaultFactory();
_130
const vault: Vault | BrowserVault = createVault();
_130
const session = ref<Session | null>(null);
_130
_130
const initializeVault = async (): Promise<void> => {
_130
try {
_130
await vault.initialize({
_130
key: 'io.ionic.gettingstartediv',
_130
type: VaultType.InMemory,
_130
deviceSecurityType: DeviceSecurityType.None,
_130
lockAfterBackgrounded: 30000,
_130
});
_130
} catch (e: unknown) {
_130
await vault.clear();
_130
await updateUnlockMode('InMemory');
_130
}
_130
_130
await enhanceVault();
_130
_130
vault.onLock(() => (session.value = null));
_130
};
_130
_130
const enhanceVault = async (): Promise<void> => {
_130
if (!(await vault.isEmpty())) {
_130
return;
_130
}
_130
_130
if (await Device.isSystemPasscodeSet()) {
_130
await updateUnlockMode('BiometricsWithPasscode');
_130
} else {
_130
await updateUnlockMode('InMemory');
_130
}
_130
};
_130
_130
const storeSession = async (s: Session): Promise<void> => {
_130
vault.setValue('session', s);
_130
session.value = s;
_130
};
_130
_130
const getSession = async (): Promise<void> => {
_130
if (await vault.isEmpty()) {
_130
session.value = null;
_130
} else {
_130
session.value = await vault.getValue<Session>('session');
_130
}
_130
};
_130
_130
const clearSession = async (): Promise<void> => {
_130
await vault.clear();
_130
session.value = null;
_130
};
_130
_130
const lockSession = async (): Promise<void> => {
_130
await vault.lock();
_130
session.value = null;
_130
};
_130
_130
const unlockSession = async (): Promise<void> => {
_130
await vault.unlock();
_130
session.value = await vault.getValue<Session>('session');
_130
};
_130
_130
const sessionIsLocked = async (): Promise<boolean> => {
_130
return (
_130
vault.config?.type !== VaultType.SecureStorage &&
_130
vault.config?.type !== VaultType.InMemory &&
_130
!(await vault.isEmpty()) &&
_130
(await vault.isLocked())
_130
);
_130
};
_130
_130
const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_130
const type = getVaultType(mode);
_130
const deviceSecurityType = type === VaultType.DeviceSecurity ? DeviceSecurityType.Both : DeviceSecurityType.None;
_130
const lockAfterBackgrounded = type === VaultType.InMemory ? 30000 : 2000;
_130
await vault.updateConfig({
_130
...(vault.config as IdentityVaultConfig),
_130
type,
_130
deviceSecurityType,
_130
lockAfterBackgrounded,
_130
});
_130
};
_130
_130
const getVaultType = async (mode: UnlockMode): Promise<VaultType> => {
_130
if (mode === 'BiometricsWithPasscode') {
_130
await provisionBiometrics();
_130
return (await Device.isBiometricsEnabled()) &&
_130
(await Device.isBiometricsAllowed()) !== BiometricPermissionState.Granted
_130
? VaultType.InMemory
_130
: VaultType.DeviceSecurity;
_130
}
_130
_130
return mode === 'InMemory' ? VaultType.InMemory : VaultType.SecureStorage;
_130
};
_130
_130
const provisionBiometrics = async (): Promise<void> => {
_130
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_130
try {
_130
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_130
} catch (error) {
_130
null;
_130
}
_130
}
_130
};
_130
_130
export const useSessionVault = (): any => ({
_130
clearSession,
_130
enhanceVault,
_130
getSession,
_130
initializeVault,
_130
lockSession,
_130
session,
_130
storeSession,
_130
updateUnlockMode,
_130
});

The application now asks for Face ID permission only if needed and only after a successful login rather than doing so as part of the startup process. In your application you may want to tie this to something else such as setting a "Use Biometrics" preference toggle. The choice is up to you.

On the Tab1Page, the user can currently click the "Use Biometrics" button even if the user has rejected Face ID. According to the rules we are enforcing, though, we will end up using an "In Memory" vault in that case. We can enhance the "disable biometrics" code that we added earlier.

src/views/Tab1Page.vue

_102
<template>
_102
<ion-page>
_102
<ion-header>
_102
<ion-toolbar>
_102
<ion-title>Tab 1</ion-title>
_102
</ion-toolbar>
_102
</ion-header>
_102
<ion-content :fullscreen="true">
_102
<ion-header collapse="condense">
_102
<ion-toolbar>
_102
<ion-title size="large">Tab 1</ion-title>
_102
</ion-toolbar>
_102
</ion-header>
_102
_102
!(await Device.isBiometricsEnabled()) || (await Device.isBiometricsAllowed()) !== BiometricPermissionState.Granted;

Next Steps

We have examined the Device API and several potential uses for it. Continue to explore how this API can be used to enhance the user experience within your application.