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 Tab2 page to demonstrate 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 Tab2 page to display the results of these methods.

src/pages/Tab2.tsx

_67
import { Device } from '@ionic-enterprise/identity-vault';
_67
import {
_67
IonContent,
_67
IonHeader,
_67
IonItem,
_67
IonLabel,
_67
IonList,
_67
IonListHeader,
_67
IonNote,
_67
IonPage,
_67
IonTitle,
_67
IonToolbar,
_67
} from '@ionic/react';
_67
import { useEffect, useState } from 'react';
_67
import './Tab2.css';
_67
_67
const Tab2: React.FC = () => {
_67
const [hasSecureHardware, setHasSecureHardware] = useState(false);
_67
const [isBiometricsSupported, setIsBiometricsSupported] = useState(false);
_67
const [availableHardware, setAvailableHardware] = useState<Array<string>>([]);
_67
_67
useEffect(() => {
_67
(async () => {
_67
setHasSecureHardware(await Device.hasSecureHardware());
_67
setIsBiometricsSupported(await Device.isBiometricsSupported());
_67
setAvailableHardware(await Device.getAvailableHardware());
_67
})();
_67
}, []);
_67
_67
return (
_67
<IonPage>
_67
<IonHeader>
_67
<IonToolbar>
_67
<IonTitle>Tab 2</IonTitle>
_67
</IonToolbar>
_67
</IonHeader>
_67
<IonContent fullscreen>
_67
<IonHeader collapse="condense">
_67
<IonToolbar>
_67
<IonTitle size="large">Tab 2</IonTitle>
_67
</IonToolbar>
_67
</IonHeader>
_67
<IonList>
_67
<IonListHeader>
_67
<IonLabel>Capabilities</IonLabel>
_67
</IonListHeader>
_67
<IonItem>
_67
<IonLabel>Secure Hardware</IonLabel>
_67
<IonNote slot="end">{hasSecureHardware?.toString()}</IonNote>
_67
</IonItem>
_67
<IonItem>
_67
<IonLabel>Biometrics Supported</IonLabel>
_67
<IonNote slot="end">{isBiometricsSupported?.toString()}</IonNote>
_67
</IonItem>
_67
<IonItem>
_67
<div>
_67
<p>Available Biometric Hardware:</p>
_67
<ul>{availableHardware?.length ? availableHardware.map((h) => <li key={h}>{h}</li>) : <li>None</li>}</ul>
_67
</div>
_67
</IonItem>
_67
</IonList>
_67
</IonContent>
_67
</IonPage>
_67
);
_67
};
_67
_67
export default Tab2;

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 one of the following:

    • Prompt: The user needs to be asked for permission to use Face ID.
    • Denied: The user has declined permission.
    • Granted: 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 Tab2 page to display the results of these methods.

src/pages/Tab2.tsx

_106
import { Device } from '@ionic-enterprise/identity-vault';
_106
import {
_106
IonContent,
_106
IonHeader,
_106
IonItem,
_106
IonLabel,
_106
IonList,
_106
IonListHeader,
_106
IonNote,
_106
IonPage,
_106
IonTitle,
_106
IonToolbar,
_106
} from '@ionic/react';
_106
import { useEffect, useState } from 'react';
_106
import './Tab2.css';
_106
_106
const Tab2: React.FC = () => {
_106
const [hasSecureHardware, setHasSecureHardware] = useState(false);
_106
const [isBiometricsSupported, setIsBiometricsSupported] = useState(false);
_106
const [availableHardware, setAvailableHardware] = useState<Array<string>>([]);
_106
const [biometricStrengthLevel, setBiometricStrengthLevel] = useState('');
_106
const [isBiometricsAllowed, setIsBiometricsAllowed] = useState('');
_106
const [isBiometricsEnabled, setIsBiometricsEnabled] = useState(false);
_106
const [isHideScreenOnBackgroundEnabled, setIsHideScreenOnBackgroundEnabled] = useState(false);
_106
const [isLockedOutOfBiometrics, setIsLockedOutOfBiometrics] = useState(false);
_106
const [isSystemPasscodeSet, setIsSystemPasscodeSet] = useState(false);
_106
_106
useEffect(() => {
_106
(async () => {
_106
setHasSecureHardware(await Device.hasSecureHardware());
_106
setIsBiometricsSupported(await Device.isBiometricsSupported());
_106
setAvailableHardware(await Device.getAvailableHardware());
_106
setBiometricStrengthLevel(await Device.getBiometricStrengthLevel());
_106
setIsBiometricsAllowed(await Device.isBiometricsAllowed());
_106
setIsBiometricsEnabled(await Device.isBiometricsEnabled());
_106
setIsHideScreenOnBackgroundEnabled(await Device.isHideScreenOnBackgroundEnabled());
_106
setIsLockedOutOfBiometrics(await Device.isLockedOutOfBiometrics());
_106
setIsSystemPasscodeSet(await Device.isSystemPasscodeSet());
_106
})();
_106
}, []);
_106
_106
return (
_106
<IonPage>
_106
<IonHeader>
_106
<IonToolbar>
_106
<IonTitle>Tab 2</IonTitle>
_106
</IonToolbar>
_106
</IonHeader>
_106
<IonContent fullscreen>
_106
<IonHeader collapse="condense">
_106
<IonToolbar>
_106
<IonTitle size="large">Tab 2</IonTitle>
_106
</IonToolbar>
_106
</IonHeader>
_106
<IonList>
_106
<IonListHeader>
_106
<IonLabel>Capabilities</IonLabel>
_106
</IonListHeader>
_106
<IonItem>
_106
<IonLabel>Secure Hardware</IonLabel>
_106
<IonNote slot="end">{hasSecureHardware?.toString()}</IonNote>
_106
</IonItem>
_106
<IonItem>
_106
<IonLabel>Biometrics Supported</IonLabel>
_106
<IonNote slot="end">{isBiometricsSupported?.toString()}</IonNote>
_106
</IonItem>
_106
<IonItem>
_106
<div>
_106
<p>Available Biometric Hardware:</p>
_106
<ul>{availableHardware?.length ? availableHardware.map((h) => <li key={h}>{h}</li>) : <li>None</li>}</ul>
_106
</div>
_106
</IonItem>
_106
<IonListHeader>
_106
<IonLabel>Configuration and Status</IonLabel>
_106
</IonListHeader>
_106
<IonItem>
_106
<IonLabel>Biometric Strength Level</IonLabel>
_106
<IonNote slot="end">{biometricStrengthLevel}</IonNote>
_106
</IonItem>
_106
<IonItem>

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 Tab2 page to call these methods.

src/pages/Tab2.tsx

_140
import { Device } from '@ionic-enterprise/identity-vault';
_140
import {
_140
IonButton,
_140
IonContent,
_140
IonHeader,
_140
IonItem,
_140
IonLabel,
_140
IonList,
_140
IonListHeader,
_140
IonNote,
_140
IonPage,
_140
IonTitle,
_140
IonToolbar,
_140
} from '@ionic/react';
_140
import { useEffect, useState } from 'react';
_140
import './Tab2.css';
_140
_140
const Tab2: React.FC = () => {
_140
const [hasSecureHardware, setHasSecureHardware] = useState(false);
_140
const [isBiometricsSupported, setIsBiometricsSupported] = useState(false);
_140
const [availableHardware, setAvailableHardware] = useState<Array<string>>([]);
_140
const [biometricStrengthLevel, setBiometricStrengthLevel] = useState('');
_140
const [isBiometricsAllowed, setIsBiometricsAllowed] = useState('');
_140
const [isBiometricsEnabled, setIsBiometricsEnabled] = useState(false);
_140
const [isHideScreenOnBackgroundEnabled, setIsHideScreenOnBackgroundEnabled] = useState(false);
_140
const [isLockedOutOfBiometrics, setIsLockedOutOfBiometrics] = useState(false);
_140
const [isSystemPasscodeSet, setIsSystemPasscodeSet] = useState(false);
_140
_140
useEffect(() => {
_140
(async () => {
_140
setHasSecureHardware(await Device.hasSecureHardware());
_140
setIsBiometricsSupported(await Device.isBiometricsSupported());
_140
setAvailableHardware(await Device.getAvailableHardware());
_140
setBiometricStrengthLevel(await Device.getBiometricStrengthLevel());
_140
setIsBiometricsAllowed(await Device.isBiometricsAllowed());
_140
setIsBiometricsEnabled(await Device.isBiometricsEnabled());
_140
setIsHideScreenOnBackgroundEnabled(await Device.isHideScreenOnBackgroundEnabled());
_140
setIsLockedOutOfBiometrics(await Device.isLockedOutOfBiometrics());
_140
setIsSystemPasscodeSet(await Device.isSystemPasscodeSet());
_140
})();
_140
}, []);
_140
_140
const toggleHideScreenOnBackground = async (): Promise<void> => {
_140
await Device.setHideScreenOnBackground(!isHideScreenOnBackgroundEnabled);
_140
setIsHideScreenOnBackgroundEnabled(await Device.isHideScreenOnBackgroundEnabled());
_140
};
_140
_140
const showBiometricPrompt = async (): Promise<void> => {
_140
try {
_140
await Device.showBiometricPrompt({
_140
iosBiometricsLocalizedReason: 'Just to show you how this works',
_140
});
_140
} catch (e) {
_140
// This is the most likely scenario
_140
alert('user cancelled biometrics prompt');
_140
}
_140
};
_140
_140
return (
_140
<IonPage>
_140
<IonHeader>
_140
<IonToolbar>
_140
<IonTitle>Tab 2</IonTitle>
_140
</IonToolbar>
_140
</IonHeader>
_140
<IonContent fullscreen>
_140
<IonHeader collapse="condense">
_140
<IonToolbar>
_140
<IonTitle size="large">Tab 2</IonTitle>
_140
</IonToolbar>
_140
</IonHeader>
_140
<IonList>
_140
<IonListHeader>
_140
<IonLabel>Capabilities</IonLabel>
_140
</IonListHeader>
_140
<IonItem>
_140
<IonLabel>Secure Hardware</IonLabel>
_140
<IonNote slot="end">{hasSecureHardware?.toString()}</IonNote>
_140
</IonItem>
_140
<IonItem>
_140
<IonLabel>Biometrics Supported</IonLabel>
_140
<IonNote slot="end">{isBiometricsSupported?.toString()}</IonNote>
_140
</IonItem>
_140
<IonItem>
_140
<div>
_140
<p>Available Biometric Hardware:</p>
_140
<ul>{availableHardware?.length ? availableHardware.map((h) => <li key={h}>{h}</li>) : <li>None</li>}</ul>
_140
</div>
_140
</IonItem>
_140
<IonListHeader>
_140
<IonLabel>Configuration and Status</IonLabel>
_140
</IonListHeader>
_140
<IonItem>
_140
<IonLabel>Biometric Strength Level</IonLabel>
_140
<IonNote slot="end">{biometricStrengthLevel}</IonNote>
_140
</IonItem>
_140
<IonItem>
_140
<IonLabel>Biometric Allowed</IonLabel>
_140
<IonNote slot="end">{isBiometricsAllowed}</IonNote>
_140
</IonItem>
_140
<IonItem>
_140
<IonLabel>Biometrics Enabled</IonLabel>
_140
<IonNote slot="end">{isBiometricsEnabled.toString()}</IonNote>
_140
</IonItem>
_140
<IonItem>
_140
<IonLabel>Hide Screen Enabled</IonLabel>
_140
<IonNote slot="end">{isHideScreenOnBackgroundEnabled.toString()}</IonNote>
_140
</IonItem>
_140
<IonItem>
_140
<IonLabel>Locked Out of Biometrics</IonLabel>
_140
<IonNote slot="end">{isLockedOutOfBiometrics.toString()}</IonNote>
_140
</IonItem>
_140
<IonItem>
_140
<IonLabel>System Passcode Set</IonLabel>
_140
<IonNote slot="end">{isSystemPasscodeSet.toString()}</IonNote>
_140
</IonItem>
_140
<IonListHeader>
_140
<IonLabel>Actions</IonLabel>
_140
</IonListHeader>
_140
<IonItem>
_140
<IonLabel>
_140
<IonButton expand="block" disabled={!isBiometricsEnabled} onClick={showBiometricPrompt}>
_140
Show Biometric Prompt
_140
</IonButton>
_140
</IonLabel>
_140
</IonItem>
_140
<IonItem>
_140
<IonLabel>
_140
<IonButton expand="block" onClick={toggleHideScreenOnBackground}>
_140
{isHideScreenOnBackgroundEnabled ? 'Disable' : 'Enable'} Security Screen
_140
</IonButton>
_140
</IonLabel>
_140
</IonItem>
_140
</IonList>
_140
</IonContent>
_140
</IonPage>
_140
);
_140
};
_140
_140
export default Tab2;

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/pages/Tab1.tsx

_102
import {
_102
IonButton,
_102
IonContent,
_102
IonHeader,
_102
IonItem,
_102
IonLabel,
_102
IonList,
_102
IonPage,
_102
IonTitle,
_102
IonToolbar,
_102
} from '@ionic/react';
_102
import { useHistory } from 'react-router';
_102
import { logout } from '../util/authentication';
_102
import { useSession } from '../util/session-store';
_102
import { updateUnlockMode } from '../util/session-vault';
_102
import './Tab1.css';
_102
import { useEffect, useState } from 'react';
_102
import { Device } from '@ionic-enterprise/identity-vault';
_102
_102
const Tab1: React.FC = () => {
_102
const history = useHistory();
_102
const { session } = useSession();
_102
const [disableBiometrics, setDisableBiometrics] = useState(false);
_102
_102
useEffect(() => {
_102
(async () => setDisableBiometrics(!(await Device.isBiometricsEnabled())))();
_102
}, []);
_102
_102
const logoutClicked = async () => {
_102
await logout();
_102
history.replace('/login');
_102
};
_102
_102
return (
_102
<IonPage>
_102
<IonHeader>
_102
<IonToolbar>
_102
<IonTitle>Tab 1</IonTitle>
_102
</IonToolbar>
_102
</IonHeader>
_102
<IonContent fullscreen>
_102
<IonHeader collapse="condense">
_102
<IonToolbar>
_102
<IonTitle size="large">Tab 1</IonTitle>

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 currently displayed 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 is shown instead. On iOS, the splash screen is displayed.

In the Tab2 page, 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

_17
import React from 'react';
_17
import { createRoot } from 'react-dom/client';
_17
import App from './App';
_17
_17
import { initializeVault } from './util/session-vault';
_17
import { Device } from '@ionic-enterprise/identity-vault';
_17
_17
const container = document.getElementById('root');
_17
const root = createRoot(container!);
_17
Device.setHideScreenOnBackground(true);
_17
initializeVault().then(() =>
_17
root.render(
_17
<React.StrictMode>
_17
<App />
_17
</React.StrictMode>,
_17
),
_17
);

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 both are 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/util/session-vault.ts

_89
import {
_89
BrowserVault,
_89
Vault,
_89
VaultType,
_89
DeviceSecurityType,
_89
IdentityVaultConfig,
_89
} from '@ionic-enterprise/identity-vault';
_89
import { createVault } from './vault-factory';
_89
import { Session } from '../models/Session';
_89
import { setState } from './session-store';
_89
_89
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_89
_89
const vault: Vault | BrowserVault = createVault();
_89
_89
export const initializeVault = async (): Promise<void> => {
_89
try {
_89
await vault.initialize({
_89
key: 'io.ionic.gettingstartedivreact',
_89
type: VaultType.SecureStorage,
_89
deviceSecurityType: DeviceSecurityType.None,
_89
lockAfterBackgrounded: 2000,
_89
});
_89
} catch (e: unknown) {
_89
await vault.clear();
_89
await updateUnlockMode('SecureStorage');
_89
}
_89
_89
vault.onLock(() => {
_89
setState({ session: null });
_89
});
_89
};
_89
_89
export const storeSession = async (session: Session): Promise<void> => {
_89
vault.setValue('session', session);
_89
setState({ session });
_89
};
_89
_89
export const restoreSession = async (): Promise<Session | null> => {
_89
let session: Session | null = null;
_89
if (!(await vault.isEmpty())) {
_89
session = await vault.getValue<Session>('session');
_89
}
_89
setState({ session });
_89
return session;
_89
};
_89
_89
export const clearSession = async (): Promise<void> => {
_89
await vault.clear();
_89
setState({ session: null });
_89
};
_89
_89
export const sessionIsLocked = async (): Promise<boolean> => {
_89
return (
_89
vault.config?.type !== VaultType.SecureStorage &&
_89
vault.config?.type !== VaultType.InMemory &&
_89
!(await vault.isEmpty()) &&
_89
(await vault.isLocked())
_89
);
_89
};
_89
_89
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_89
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_89
_89
switch (mode) {
_89
case 'BiometricsWithPasscode': {
_89
newConfig.type = VaultType.DeviceSecurity;
_89
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_89
break;
_89
}
_89
case 'InMemory': {
_89
newConfig.type = VaultType.InMemory;
_89
newConfig.deviceSecurityType = DeviceSecurityType.None;
_89
break;
_89
}
_89
default: {
_89
newConfig.type = VaultType.SecureStorage;
_89
newConfig.deviceSecurityType = DeviceSecurityType.None;
_89
break;
_89
}
_89
}
_89
_89
await vault.updateConfig(newConfig);
_89
};
_89
_89
export const lockSession = async (): Promise<void> => {
_89
await vault.lock();
_89
setState({ session: null });
_89
};

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

src/util/session-vault.ts

_90
import {
_90
BrowserVault,
_90
Device,
_90
Vault,
_90
VaultType,
_90
DeviceSecurityType,
_90
IdentityVaultConfig,
_90
} from '@ionic-enterprise/identity-vault';
_90
import { createVault } from './vault-factory';
_90
import { Session } from '../models/Session';
_90
import { setState } from './session-store';
_90
_90
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_90
_90
const vault: Vault | BrowserVault = createVault();
_90
_90
export const initializeVault = async (): Promise<void> => {
_90
try {
_90
await vault.initialize({
_90
key: 'io.ionic.gettingstartedivreact',
_90
type: VaultType.InMemory,
_90
deviceSecurityType: DeviceSecurityType.None,
_90
lockAfterBackgrounded: 30000,
_90
});
_90
} catch (e: unknown) {
_90
await vault.clear();
_90
await updateUnlockMode('InMemory');
_90
}
_90
_90
vault.onLock(() => {
_90
setState({ session: null });
_90
});
_90
};
_90
_90
export const storeSession = async (session: Session): Promise<void> => {
_90
vault.setValue('session', session);
_90
setState({ session });
_90
};
_90
_90
export const restoreSession = async (): Promise<Session | null> => {
_90
let session: Session | null = null;
_90
if (!(await vault.isEmpty())) {
_90
session = await vault.getValue<Session>('session');
_90
}
_90
setState({ session });
_90
return session;
_90
};
_90
_90
export const clearSession = async (): Promise<void> => {
_90
await vault.clear();
_90
setState({ session: null });
_90
};
_90
_90
export const sessionIsLocked = async (): Promise<boolean> => {
_90
return (
_90
vault.config?.type !== VaultType.SecureStorage &&
_90
vault.config?.type !== VaultType.InMemory &&
_90
!(await vault.isEmpty()) &&
_90
(await vault.isLocked())
_90
);
_90
};
_90
_90
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_90
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_90
_90
switch (mode) {
_90
case 'BiometricsWithPasscode': {
_90
newConfig.type = VaultType.DeviceSecurity;
_90
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_90
break;
_90
}
_90
case 'InMemory': {
_90
newConfig.type = VaultType.InMemory;
_90
newConfig.deviceSecurityType = DeviceSecurityType.None;
_90
break;
_90
}
_90
default: {
_90
newConfig.type = VaultType.SecureStorage;
_90
newConfig.deviceSecurityType = DeviceSecurityType.None;
_90
break;
_90
}
_90
}
_90
_90
await vault.updateConfig(newConfig);
_90
};
_90
_90
export const lockSession = async (): Promise<void> => {
_90
await vault.lock();
_90
setState({ session: null });
_90
};

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/util/session-vault.ts

_98
import {
_98
BrowserVault,
_98
Device,
_98
Vault,
_98
VaultType,
_98
DeviceSecurityType,
_98
IdentityVaultConfig,
_98
} from '@ionic-enterprise/identity-vault';
_98
import { createVault } from './vault-factory';
_98
import { Session } from '../models/Session';
_98
import { setState } from './session-store';
_98
_98
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_98
_98
const vault: Vault | BrowserVault = createVault();
_98
_98
export const initializeVault = async (): Promise<void> => {
_98
try {
_98
await vault.initialize({
_98
key: 'io.ionic.gettingstartedivreact',
_98
type: VaultType.InMemory,
_98
deviceSecurityType: DeviceSecurityType.None,
_98
lockAfterBackgrounded: 30000,
_98
});
_98
} catch (e: unknown) {
_98
await vault.clear();
_98
await updateUnlockMode('SecureStorage');
_98
}
_98
_98
await enhanceVault();
_98
_98
vault.onLock(() => {
_98
setState({ session: null });
_98
});
_98
};
_98
_98
const enhanceVault = async (): Promise<void> => {
_98
if (!(await vault.isEmpty())) {
_98
return;
_98
}
_98
};
_98
_98
export const storeSession = async (session: Session): Promise<void> => {
_98
vault.setValue('session', session);
_98
setState({ session });
_98
};
_98
_98
export const restoreSession = async (): Promise<Session | null> => {
_98
let session: Session | null = null;
_98
if (!(await vault.isEmpty())) {
_98
session = await vault.getValue<Session>('session');
_98
}
_98
setState({ session });
_98
return session;
_98
};
_98
_98
export const clearSession = async (): Promise<void> => {
_98
await vault.clear();
_98
setState({ session: null });
_98
};
_98
_98
export const sessionIsLocked = async (): Promise<boolean> => {
_98
return (
_98
vault.config?.type !== VaultType.SecureStorage &&
_98
vault.config?.type !== VaultType.InMemory &&
_98
!(await vault.isEmpty()) &&
_98
(await vault.isLocked())
_98
);
_98
};
_98
_98
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_98
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_98
_98
switch (mode) {
_98
case 'BiometricsWithPasscode': {
_98
newConfig.type = VaultType.DeviceSecurity;
_98
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_98
break;
_98
}
_98
case 'InMemory': {
_98
newConfig.type = VaultType.InMemory;
_98
newConfig.deviceSecurityType = DeviceSecurityType.None;
_98
break;
_98
}
_98
default: {
_98
newConfig.type = VaultType.SecureStorage;
_98
newConfig.deviceSecurityType = DeviceSecurityType.None;
_98
break;
_98
}
_98
}
_98
_98
await vault.updateConfig(newConfig);
_98
};
_98
_98
export const lockSession = async (): Promise<void> => {
_98
await vault.lock();
_98
setState({ session: null });
_98
};

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/util/session-vault.ts

_104
import {
_104
BrowserVault,
_104
Device,
_104
Vault,
_104
VaultType,
_104
DeviceSecurityType,
_104
IdentityVaultConfig,
_104
} from '@ionic-enterprise/identity-vault';
_104
import { createVault } from './vault-factory';
_104
import { Session } from '../models/Session';
_104
import { setState } from './session-store';
_104
_104
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_104
_104
const vault: Vault | BrowserVault = createVault();
_104
_104
export const initializeVault = async (): Promise<void> => {
_104
try {
_104
await vault.initialize({
_104
key: 'io.ionic.gettingstartedivreact',
_104
type: VaultType.InMemory,
_104
deviceSecurityType: DeviceSecurityType.None,
_104
lockAfterBackgrounded: 30000,
_104
});
_104
} catch (e: unknown) {
_104
await vault.clear();
_104
await updateUnlockMode('SecureStorage');
_104
}
_104
_104
await enhanceVault();
_104
_104
vault.onLock(() => {
_104
setState({ session: null });
_104
});
_104
};
_104
_104
const enhanceVault = async (): Promise<void> => {
_104
if (!(await vault.isEmpty())) {
_104
return;
_104
}
_104
_104
if (await Device.isSystemPasscodeSet()) {
_104
await updateUnlockMode('BiometricsWithPasscode');
_104
} else {
_104
await updateUnlockMode('InMemory');
_104
}
_104
};
_104
_104
export const storeSession = async (session: Session): Promise<void> => {
_104
vault.setValue('session', session);
_104
setState({ session });
_104
};
_104
_104
export const restoreSession = async (): Promise<Session | null> => {
_104
let session: Session | null = null;
_104
if (!(await vault.isEmpty())) {
_104
session = await vault.getValue<Session>('session');
_104
}
_104
setState({ session });
_104
return session;
_104
};
_104
_104
export const clearSession = async (): Promise<void> => {
_104
await vault.clear();
_104
setState({ session: null });
_104
};
_104
_104
export const sessionIsLocked = async (): Promise<boolean> => {
_104
return (
_104
vault.config?.type !== VaultType.SecureStorage &&
_104
vault.config?.type !== VaultType.InMemory &&
_104
!(await vault.isEmpty()) &&
_104
(await vault.isLocked())
_104
);
_104
};
_104
_104
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_104
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_104
_104
switch (mode) {
_104
case 'BiometricsWithPasscode': {
_104
newConfig.type = VaultType.DeviceSecurity;
_104
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_104
break;
_104
}
_104
case 'InMemory': {
_104
newConfig.type = VaultType.InMemory;
_104
newConfig.deviceSecurityType = DeviceSecurityType.None;
_104
break;
_104
}
_104
default: {
_104
newConfig.type = VaultType.SecureStorage;
_104
newConfig.deviceSecurityType = DeviceSecurityType.None;
_104
break;
_104
}
_104
}
_104
_104
await vault.updateConfig(newConfig);
_104
};
_104
_104
export const lockSession = async (): Promise<void> => {
_104
await vault.lock();
_104
setState({ session: null });
_104
};

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

src/util/session-vault.ts

_105
import {
_105
BrowserVault,
_105
Device,
_105
Vault,
_105
VaultType,
_105
DeviceSecurityType,
_105
IdentityVaultConfig,
_105
} from '@ionic-enterprise/identity-vault';
_105
import { createVault } from './vault-factory';
_105
import { Session } from '../models/Session';
_105
import { setState } from './session-store';
_105
_105
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_105
_105
const vault: Vault | BrowserVault = createVault();
_105
_105
export const initializeVault = async (): Promise<void> => {
_105
try {
_105
await vault.initialize({
_105
key: 'io.ionic.gettingstartedivreact',
_105
type: VaultType.InMemory,
_105
deviceSecurityType: DeviceSecurityType.None,
_105
lockAfterBackgrounded: 30000,
_105
});
_105
} catch (e: unknown) {
_105
await vault.clear();
_105
await updateUnlockMode('SecureStorage');
_105
}
_105
_105
await enhanceVault();
_105
_105
vault.onLock(() => {
_105
setState({ session: null });
_105
});
_105
};
_105
_105
const enhanceVault = async (): Promise<void> => {
_105
if (!(await vault.isEmpty())) {
_105
return;
_105
}
_105
_105
if (await Device.isSystemPasscodeSet()) {
_105
await updateUnlockMode('BiometricsWithPasscode');
_105
} else {
_105
await updateUnlockMode('InMemory');
_105
}
_105
};
_105
_105
export const storeSession = async (session: Session): Promise<void> => {
_105
vault.setValue('session', session);
_105
setState({ session });
_105
};
_105
_105
export const restoreSession = async (): Promise<Session | null> => {
_105
let session: Session | null = null;
_105
if (!(await vault.isEmpty())) {
_105
session = await vault.getValue<Session>('session');
_105
}
_105
setState({ session });
_105
return session;
_105
};
_105
_105
export const clearSession = async (): Promise<void> => {
_105
await vault.clear();
_105
setState({ session: null });
_105
};
_105
_105
export const sessionIsLocked = async (): Promise<boolean> => {
_105
return (
_105
vault.config?.type !== VaultType.SecureStorage &&
_105
vault.config?.type !== VaultType.InMemory &&
_105
!(await vault.isEmpty()) &&
_105
(await vault.isLocked())
_105
);
_105
};
_105
_105
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_105
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_105
_105
switch (mode) {
_105
case 'BiometricsWithPasscode': {
_105
newConfig.type = VaultType.DeviceSecurity;
_105
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_105
break;
_105
}
_105
case 'InMemory': {
_105
newConfig.type = VaultType.InMemory;
_105
newConfig.deviceSecurityType = DeviceSecurityType.None;
_105
break;
_105
}
_105
default: {
_105
newConfig.type = VaultType.SecureStorage;
_105
newConfig.deviceSecurityType = DeviceSecurityType.None;
_105
break;
_105
}
_105
}
_105
newConfig.lockAfterBackgrounded = newConfig.type === VaultType.InMemory ? 30000 : 2000;
_105
_105
await vault.updateConfig(newConfig);
_105
};
_105
_105
export const lockSession = async (): Promise<void> => {
_105
await vault.lock();
_105
setState({ session: null });
_105
};

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/util/session-vault.ts

_89
import {
_89
BrowserVault,
_89
Vault,
_89
VaultType,
_89
DeviceSecurityType,
_89
IdentityVaultConfig,
_89
} from '@ionic-enterprise/identity-vault';
_89
import { createVault } from './vault-factory';
_89
import { Session } from '../models/Session';
_89
import { setState } from './session-store';
_89
_89
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_89
_89
const vault: Vault | BrowserVault = createVault();
_89
_89
export const initializeVault = async (): Promise<void> => {
_89
try {
_89
await vault.initialize({
_89
key: 'io.ionic.gettingstartedivreact',
_89
type: VaultType.SecureStorage,
_89
deviceSecurityType: DeviceSecurityType.None,
_89
lockAfterBackgrounded: 2000,
_89
});
_89
} catch (e: unknown) {
_89
await vault.clear();
_89
await updateUnlockMode('SecureStorage');
_89
}
_89
_89
vault.onLock(() => {
_89
setState({ session: null });
_89
});
_89
};
_89
_89
export const storeSession = async (session: Session): Promise<void> => {
_89
vault.setValue('session', session);
_89
setState({ session });
_89
};
_89
_89
export const restoreSession = async (): Promise<Session | null> => {
_89
let session: Session | null = null;
_89
if (!(await vault.isEmpty())) {
_89
session = await vault.getValue<Session>('session');
_89
}
_89
setState({ session });
_89
return session;
_89
};
_89
_89
export const clearSession = async (): Promise<void> => {
_89
await vault.clear();
_89
setState({ session: null });
_89
};
_89
_89
export const sessionIsLocked = async (): Promise<boolean> => {
_89
return (
_89
vault.config?.type !== VaultType.SecureStorage &&
_89
vault.config?.type !== VaultType.InMemory &&
_89
!(await vault.isEmpty()) &&
_89
(await vault.isLocked())
_89
);
_89
};
_89
_89
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_89
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_89
_89
switch (mode) {
_89
case 'BiometricsWithPasscode': {
_89
newConfig.type = VaultType.DeviceSecurity;
_89
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_89
break;
_89
}
_89
case 'InMemory': {
_89
newConfig.type = VaultType.InMemory;
_89
newConfig.deviceSecurityType = DeviceSecurityType.None;
_89
break;
_89
}
_89
default: {
_89
newConfig.type = VaultType.SecureStorage;
_89
newConfig.deviceSecurityType = DeviceSecurityType.None;
_89
break;
_89
}
_89
}
_89
_89
await vault.updateConfig(newConfig);
_89
};
_89
_89
export const lockSession = async (): Promise<void> => {
_89
await vault.lock();
_89
setState({ session: null });
_89
};

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. We have made this timeout longer than our standard 2 second timeout because the InMemory vault will destroy the authentication result after the timeout rather than storing it.
  • 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/util/session-vault.ts

_106
import {
_106
BiometricPermissionState,
_106
BrowserVault,
_106
Device,
_106
Vault,
_106
VaultType,
_106
DeviceSecurityType,
_106
IdentityVaultConfig,
_106
} from '@ionic-enterprise/identity-vault';
_106
import { createVault } from './vault-factory';
_106
import { Session } from '../models/Session';
_106
import { setState } from './session-store';
_106
_106
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_106
_106
const vault: Vault | BrowserVault = createVault();
_106
_106
export const initializeVault = async (): Promise<void> => {
_106
try {
_106
await vault.initialize({
_106
key: 'io.ionic.gettingstartedivreact',
_106
type: VaultType.InMemory,
_106
deviceSecurityType: DeviceSecurityType.None,
_106
lockAfterBackgrounded: 30000,
_106
});
_106
} catch (e: unknown) {
_106
await vault.clear();
_106
await updateUnlockMode('SecureStorage');
_106
}
_106
_106
await enhanceVault();
_106
_106
vault.onLock(() => {
_106
setState({ session: null });
_106
});
_106
};
_106
_106
const enhanceVault = async (): Promise<void> => {
_106
if (!(await vault.isEmpty())) {
_106
return;
_106
}
_106
_106
if (await Device.isSystemPasscodeSet()) {
_106
await updateUnlockMode('BiometricsWithPasscode');
_106
} else {
_106
await updateUnlockMode('InMemory');
_106
}
_106
};
_106
_106
export const storeSession = async (session: Session): Promise<void> => {
_106
vault.setValue('session', session);
_106
setState({ session });
_106
};
_106
_106
export const restoreSession = async (): Promise<Session | null> => {
_106
let session: Session | null = null;
_106
if (!(await vault.isEmpty())) {
_106
session = await vault.getValue<Session>('session');
_106
}
_106
setState({ session });
_106
return session;
_106
};
_106
_106
export const clearSession = async (): Promise<void> => {
_106
await vault.clear();
_106
setState({ session: null });
_106
};
_106
_106
export const sessionIsLocked = async (): Promise<boolean> => {
_106
return (
_106
vault.config?.type !== VaultType.SecureStorage &&
_106
vault.config?.type !== VaultType.InMemory &&
_106
!(await vault.isEmpty()) &&
_106
(await vault.isLocked())
_106
);
_106
};
_106
_106
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_106
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_106
_106
switch (mode) {
_106
case 'BiometricsWithPasscode': {
_106
newConfig.type = VaultType.DeviceSecurity;
_106
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_106
break;
_106
}
_106
case 'InMemory': {
_106
newConfig.type = VaultType.InMemory;
_106
newConfig.deviceSecurityType = DeviceSecurityType.None;
_106
break;
_106
}
_106
default: {
_106
newConfig.type = VaultType.SecureStorage;
_106
newConfig.deviceSecurityType = DeviceSecurityType.None;
_106
break;
_106
}
_106
}
_106
newConfig.lockAfterBackgrounded = newConfig.type === VaultType.InMemory ? 30000 : 2000;
_106
_106
await vault.updateConfig(newConfig);
_106
};
_106
_106
export const lockSession = async (): Promise<void> => {
_106
await vault.lock();
_106
setState({ session: null });
_106
};

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

src/util/session-vault.ts

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

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

src/util/session-vault.ts

_120
import {
_120
BiometricPermissionState,
_120
BrowserVault,
_120
Device,
_120
Vault,
_120
VaultType,
_120
DeviceSecurityType,
_120
IdentityVaultConfig,
_120
} from '@ionic-enterprise/identity-vault';
_120
import { createVault } from './vault-factory';
_120
import { Session } from '../models/Session';
_120
import { setState } from './session-store';
_120
_120
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_120
_120
const vault: Vault | BrowserVault = createVault();
_120
_120
export const initializeVault = async (): Promise<void> => {
_120
try {
_120
await vault.initialize({
_120
key: 'io.ionic.gettingstartedivreact',
_120
type: VaultType.InMemory,
_120
deviceSecurityType: DeviceSecurityType.None,
_120
lockAfterBackgrounded: 30000,
_120
});
_120
} catch (e: unknown) {
_120
await vault.clear();
_120
await updateUnlockMode('SecureStorage');
_120
}
_120
_120
await enhanceVault();
_120
_120
vault.onLock(() => {
_120
setState({ session: null });
_120
});
_120
};
_120
_120
const enhanceVault = async (): Promise<void> => {
_120
if (!(await vault.isEmpty())) {
_120
return;
_120
}
_120
_120
if (await Device.isSystemPasscodeSet()) {
_120
await updateUnlockMode('BiometricsWithPasscode');
_120
} else {
_120
await updateUnlockMode('InMemory');
_120
}
_120
};
_120
_120
export const storeSession = async (session: Session): Promise<void> => {
_120
vault.setValue('session', session);
_120
setState({ session });
_120
};
_120
_120
export const restoreSession = async (): Promise<Session | null> => {
_120
let session: Session | null = null;
_120
if (!(await vault.isEmpty())) {
_120
session = await vault.getValue<Session>('session');
_120
}
_120
setState({ session });
_120
return session;
_120
};
_120
_120
export const clearSession = async (): Promise<void> => {
_120
await vault.clear();
_120
setState({ session: null });
_120
};
_120
_120
export const sessionIsLocked = async (): Promise<boolean> => {
_120
return (
_120
vault.config?.type !== VaultType.SecureStorage &&
_120
vault.config?.type !== VaultType.InMemory &&
_120
!(await vault.isEmpty()) &&
_120
(await vault.isLocked())
_120
);
_120
};
_120
_120
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_120
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_120
_120
switch (getAllowedMode(mode)) {
_120
case 'BiometricsWithPasscode': {
_120
newConfig.type = VaultType.DeviceSecurity;
_120
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_120
break;
_120
}
_120
case 'InMemory': {
_120
newConfig.type = VaultType.InMemory;
_120
newConfig.deviceSecurityType = DeviceSecurityType.None;
_120
break;
_120
}
_120
default: {
_120
newConfig.type = VaultType.SecureStorage;
_120
newConfig.deviceSecurityType = DeviceSecurityType.None;
_120
break;
_120
}
_120
}
_120
newConfig.lockAfterBackgrounded = newConfig.type === VaultType.InMemory ? 30000 : 2000;
_120
_120
await vault.updateConfig(newConfig);
_120
};
_120
_120
const getAllowedMode = async(mode: UnlockMode): Promise<UnlockMode> => {
_120
return mode;
_120
};
_120
_120
const provisionBiometrics = async (): Promise<void> => {
_120
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_120
try {
_120
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_120
} catch (error) {
_120
null;
_120
}
_120
}
_120
};
_120
_120
export const lockSession = async (): Promise<void> => {
_120
await vault.lock();
_120
setState({ session: null });
_120
};

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

src/util/session-vault.ts

_123
import {
_123
BiometricPermissionState,
_123
BrowserVault,
_123
Device,
_123
Vault,
_123
VaultType,
_123
DeviceSecurityType,
_123
IdentityVaultConfig,
_123
} from '@ionic-enterprise/identity-vault';
_123
import { createVault } from './vault-factory';
_123
import { Session } from '../models/Session';
_123
import { setState } from './session-store';
_123
_123
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_123
_123
const vault: Vault | BrowserVault = createVault();
_123
_123
export const initializeVault = async (): Promise<void> => {
_123
try {
_123
await vault.initialize({
_123
key: 'io.ionic.gettingstartedivreact',
_123
type: VaultType.InMemory,
_123
deviceSecurityType: DeviceSecurityType.None,
_123
lockAfterBackgrounded: 30000,
_123
});
_123
} catch (e: unknown) {
_123
await vault.clear();
_123
await updateUnlockMode('SecureStorage');
_123
}
_123
_123
await enhanceVault();
_123
_123
vault.onLock(() => {
_123
setState({ session: null });
_123
});
_123
};
_123
_123
const enhanceVault = async (): Promise<void> => {
_123
if (!(await vault.isEmpty())) {
_123
return;
_123
}
_123
_123
if (await Device.isSystemPasscodeSet()) {
_123
await updateUnlockMode('BiometricsWithPasscode');
_123
} else {
_123
await updateUnlockMode('InMemory');
_123
}
_123
};
_123
_123
export const storeSession = async (session: Session): Promise<void> => {
_123
vault.setValue('session', session);
_123
setState({ session });
_123
};
_123
_123
export const restoreSession = async (): Promise<Session | null> => {
_123
let session: Session | null = null;
_123
if (!(await vault.isEmpty())) {
_123
session = await vault.getValue<Session>('session');
_123
}
_123
setState({ session });
_123
return session;
_123
};
_123
_123
export const clearSession = async (): Promise<void> => {
_123
await vault.clear();
_123
setState({ session: null });
_123
};
_123
_123
export const sessionIsLocked = async (): Promise<boolean> => {
_123
return (
_123
vault.config?.type !== VaultType.SecureStorage &&
_123
vault.config?.type !== VaultType.InMemory &&
_123
!(await vault.isEmpty()) &&
_123
(await vault.isLocked())
_123
);
_123
};
_123
_123
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_123
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_123
_123
switch (getAllowedMode(mode)) {
_123
case 'BiometricsWithPasscode': {
_123
newConfig.type = VaultType.DeviceSecurity;
_123
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_123
break;
_123
}
_123
case 'InMemory': {
_123
newConfig.type = VaultType.InMemory;
_123
newConfig.deviceSecurityType = DeviceSecurityType.None;
_123
break;
_123
}
_123
default: {
_123
newConfig.type = VaultType.SecureStorage;
_123
newConfig.deviceSecurityType = DeviceSecurityType.None;
_123
break;
_123
}
_123
}
_123
newConfig.lockAfterBackgrounded = newConfig.type === VaultType.InMemory ? 30000 : 2000;
_123
_123
await vault.updateConfig(newConfig);
_123
};
_123
_123
const getAllowedMode = async(mode: UnlockMode) :Promise<UnlockMode> => {
_123
if (mode === 'BiometricsWithPasscode') {
_123
return 'BiometricsWithPasscode';
_123
}
_123
return mode;
_123
};
_123
_123
const provisionBiometrics = async (): Promise<void> => {
_123
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_123
try {
_123
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_123
} catch (error) {
_123
null;
_123
}
_123
}
_123
};
_123
_123
export const lockSession = async (): Promise<void> => {
_123
await vault.lock();
_123
setState({ session: null });
_123
};

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

src/util/session-vault.ts

_124
import {
_124
BiometricPermissionState,
_124
BrowserVault,
_124
Device,
_124
Vault,
_124
VaultType,
_124
DeviceSecurityType,
_124
IdentityVaultConfig,
_124
} from '@ionic-enterprise/identity-vault';
_124
import { createVault } from './vault-factory';
_124
import { Session } from '../models/Session';
_124
import { setState } from './session-store';
_124
_124
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_124
_124
const vault: Vault | BrowserVault = createVault();
_124
_124
export const initializeVault = async (): Promise<void> => {
_124
try {
_124
await vault.initialize({
_124
key: 'io.ionic.gettingstartedivreact',
_124
type: VaultType.InMemory,
_124
deviceSecurityType: DeviceSecurityType.None,
_124
lockAfterBackgrounded: 30000,
_124
});
_124
} catch (e: unknown) {
_124
await vault.clear();
_124
await updateUnlockMode('SecureStorage');
_124
}
_124
_124
await enhanceVault();
_124
_124
vault.onLock(() => {
_124
setState({ session: null });
_124
});
_124
};
_124
_124
const enhanceVault = async (): Promise<void> => {
_124
if (!(await vault.isEmpty())) {
_124
return;
_124
}
_124
_124
if (await Device.isSystemPasscodeSet()) {
_124
await updateUnlockMode('BiometricsWithPasscode');
_124
} else {
_124
await updateUnlockMode('InMemory');
_124
}
_124
};
_124
_124
export const storeSession = async (session: Session): Promise<void> => {
_124
vault.setValue('session', session);
_124
setState({ session });
_124
};
_124
_124
export const restoreSession = async (): Promise<Session | null> => {
_124
let session: Session | null = null;
_124
if (!(await vault.isEmpty())) {
_124
session = await vault.getValue<Session>('session');
_124
}
_124
setState({ session });
_124
return session;
_124
};
_124
_124
export const clearSession = async (): Promise<void> => {
_124
await vault.clear();
_124
setState({ session: null });
_124
};
_124
_124
export 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
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_124
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_124
_124
switch (getAllowedMode(mode)) {
_124
case 'BiometricsWithPasscode': {
_124
newConfig.type = VaultType.DeviceSecurity;
_124
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_124
break;
_124
}
_124
case 'InMemory': {
_124
newConfig.type = VaultType.InMemory;
_124
newConfig.deviceSecurityType = DeviceSecurityType.None;
_124
break;
_124
}
_124
default: {
_124
newConfig.type = VaultType.SecureStorage;
_124
newConfig.deviceSecurityType = DeviceSecurityType.None;
_124
break;
_124
}
_124
}
_124
newConfig.lockAfterBackgrounded = newConfig.type === VaultType.InMemory ? 30000 : 2000;
_124
_124
await vault.updateConfig(newConfig);
_124
};
_124
_124
const getAllowedMode = async(mode: UnlockMode) :Promise<UnlockMode> => {
_124
if (mode === 'BiometricsWithPasscode') {
_124
await provisionBiometrics();
_124
return 'BiometricsWithPasscode';
_124
}
_124
return mode;
_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 lockSession = async (): Promise<void> => {
_124
await vault.lock();
_124
setState({ session: null });
_124
};

Provision the Biometrics.

src/util/session-vault.ts

_127
import {
_127
BiometricPermissionState,
_127
BrowserVault,
_127
Device,
_127
Vault,
_127
VaultType,
_127
DeviceSecurityType,
_127
IdentityVaultConfig,
_127
} from '@ionic-enterprise/identity-vault';
_127
import { createVault } from './vault-factory';
_127
import { Session } from '../models/Session';
_127
import { setState } from './session-store';
_127
_127
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_127
_127
const vault: Vault | BrowserVault = createVault();
_127
_127
export const initializeVault = async (): Promise<void> => {
_127
try {
_127
await vault.initialize({
_127
key: 'io.ionic.gettingstartedivreact',
_127
type: VaultType.InMemory,
_127
deviceSecurityType: DeviceSecurityType.None,
_127
lockAfterBackgrounded: 30000,
_127
});
_127
} catch (e: unknown) {
_127
await vault.clear();
_127
await updateUnlockMode('SecureStorage');
_127
}
_127
_127
await enhanceVault();
_127
_127
vault.onLock(() => {
_127
setState({ session: null });
_127
});
_127
};
_127
_127
const enhanceVault = async (): Promise<void> => {
_127
if (!(await vault.isEmpty())) {
_127
return;
_127
}
_127
_127
if (await Device.isSystemPasscodeSet()) {
_127
await updateUnlockMode('BiometricsWithPasscode');
_127
} else {
_127
await updateUnlockMode('InMemory');
_127
}
_127
};
_127
_127
export const storeSession = async (session: Session): Promise<void> => {
_127
vault.setValue('session', session);
_127
setState({ session });
_127
};
_127
_127
export const restoreSession = async (): Promise<Session | null> => {
_127
let session: Session | null = null;
_127
if (!(await vault.isEmpty())) {
_127
session = await vault.getValue<Session>('session');
_127
}
_127
setState({ session });
_127
return session;
_127
};
_127
_127
export const clearSession = async (): Promise<void> => {
_127
await vault.clear();
_127
setState({ session: null });
_127
};
_127
_127
export const sessionIsLocked = async (): Promise<boolean> => {
_127
return (
_127
vault.config?.type !== VaultType.SecureStorage &&
_127
vault.config?.type !== VaultType.InMemory &&
_127
!(await vault.isEmpty()) &&
_127
(await vault.isLocked())
_127
);
_127
};
_127
_127
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_127
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_127
_127
switch (getAllowedMode(mode)) {
_127
case 'BiometricsWithPasscode': {
_127
newConfig.type = VaultType.DeviceSecurity;
_127
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_127
break;
_127
}
_127
case 'InMemory': {
_127
newConfig.type = VaultType.InMemory;
_127
newConfig.deviceSecurityType = DeviceSecurityType.None;
_127
break;
_127
}
_127
default: {
_127
newConfig.type = VaultType.SecureStorage;
_127
newConfig.deviceSecurityType = DeviceSecurityType.None;
_127
break;
_127
}
_127
}
_127
newConfig.lockAfterBackgrounded = newConfig.type === VaultType.InMemory ? 30000 : 2000;
_127
_127
await vault.updateConfig(newConfig);
_127
};
_127
_127
const getAllowedMode = async(mode: UnlockMode) :Promise<UnlockMode> => {
_127
if (mode === 'BiometricsWithPasscode') {
_127
await provisionBiometrics();
_127
return (await Device.isBiometricsEnabled()) &&
_127
(await Device.isBiometricsAllowed()) !== BiometricPermissionState.Granted
_127
? 'InMemory'
_127
: 'BiometricsWithPasscode';
_127
}
_127
return mode;
_127
};
_127
_127
const provisionBiometrics = async (): Promise<void> => {
_127
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_127
try {
_127
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_127
} catch (error) {
_127
null;
_127
}
_127
}
_127
};
_127
_127
export const lockSession = async (): Promise<void> => {
_127
await vault.lock();
_127
setState({ session: null });
_127
};

If biometrics has been set up, return the mode 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 mode to use 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 mode based on whether the user has granted or denied access to Face ID.

src/util/session-vault.ts

_106
import {
_106
BiometricPermissionState,
_106
BrowserVault,
_106
Device,
_106
Vault,
_106
VaultType,
_106
DeviceSecurityType,
_106
IdentityVaultConfig,
_106
} from '@ionic-enterprise/identity-vault';
_106
import { createVault } from './vault-factory';
_106
import { Session } from '../models/Session';
_106
import { setState } from './session-store';
_106
_106
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_106
_106
const vault: Vault | BrowserVault = createVault();
_106
_106
export const initializeVault = async (): Promise<void> => {
_106
try {
_106
await vault.initialize({
_106
key: 'io.ionic.gettingstartedivreact',
_106
type: VaultType.InMemory,
_106
deviceSecurityType: DeviceSecurityType.None,
_106
lockAfterBackgrounded: 30000,
_106
});
_106
} catch (e: unknown) {
_106
await vault.clear();
_106
await updateUnlockMode('SecureStorage');
_106
}
_106
_106
await enhanceVault();
_106
_106
vault.onLock(() => {
_106
setState({ session: null });
_106
});
_106
};
_106
_106
const enhanceVault = async (): Promise<void> => {
_106
if (!(await vault.isEmpty())) {
_106
return;
_106
}
_106
_106
if (await Device.isSystemPasscodeSet()) {
_106
await updateUnlockMode('BiometricsWithPasscode');
_106
} else {
_106
await updateUnlockMode('InMemory');
_106
}
_106
};
_106
_106
export const storeSession = async (session: Session): Promise<void> => {
_106
vault.setValue('session', session);
_106
setState({ session });
_106
};
_106
_106
export const restoreSession = async (): Promise<Session | null> => {
_106
let session: Session | null = null;
_106
if (!(await vault.isEmpty())) {
_106
session = await vault.getValue<Session>('session');
_106
}
_106
setState({ session });
_106
return session;
_106
};
_106
_106
export const clearSession = async (): Promise<void> => {
_106
await vault.clear();
_106
setState({ session: null });
_106
};
_106
_106
export const sessionIsLocked = async (): Promise<boolean> => {
_106
return (
_106
vault.config?.type !== VaultType.SecureStorage &&
_106
vault.config?.type !== VaultType.InMemory &&
_106
!(await vault.isEmpty()) &&
_106
(await vault.isLocked())
_106
);
_106
};
_106
_106
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_106
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_106
_106
switch (mode) {
_106
case 'BiometricsWithPasscode': {
_106
newConfig.type = VaultType.DeviceSecurity;
_106
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_106
break;
_106
}
_106
case 'InMemory': {
_106
newConfig.type = VaultType.InMemory;
_106
newConfig.deviceSecurityType = DeviceSecurityType.None;
_106
break;
_106
}
_106
default: {
_106
newConfig.type = VaultType.SecureStorage;
_106
newConfig.deviceSecurityType = DeviceSecurityType.None;
_106
break;
_106
}
_106
}
_106
newConfig.lockAfterBackgrounded = newConfig.type === VaultType.InMemory ? 30000 : 2000;
_106
_106
await vault.updateConfig(newConfig);
_106
};
_106
_106
export const lockSession = async (): Promise<void> => {
_106
await vault.lock();
_106
setState({ session: null });
_106
};

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/util/session-vault.ts
src/pages/Login.tsx

_127
import {
_127
BiometricPermissionState,
_127
BrowserVault,
_127
Device,
_127
Vault,
_127
VaultType,
_127
DeviceSecurityType,
_127
IdentityVaultConfig,
_127
} from '@ionic-enterprise/identity-vault';
_127
import { createVault } from './vault-factory';
_127
import { Session } from '../models/Session';
_127
import { setState } from './session-store';
_127
_127
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_127
_127
const vault: Vault | BrowserVault = createVault();
_127
_127
export const initializeVault = async (): Promise<void> => {
_127
try {
_127
await vault.initialize({
_127
key: 'io.ionic.gettingstartedivreact',
_127
type: VaultType.InMemory,
_127
deviceSecurityType: DeviceSecurityType.None,
_127
lockAfterBackgrounded: 30000,
_127
});
_127
} catch (e: unknown) {
_127
await vault.clear();
_127
await updateUnlockMode('SecureStorage');
_127
}
_127
_127
await enhanceVault();
_127
_127
vault.onLock(() => {
_127
setState({ session: null });
_127
});
_127
};
_127
_127
export const enhanceVault = async (): Promise<void> => {
_127
if (!(await vault.isEmpty())) {
_127
return;
_127
}
_127
_127
if (await Device.isSystemPasscodeSet()) {
_127
await updateUnlockMode('BiometricsWithPasscode');
_127
} else {
_127
await updateUnlockMode('InMemory');
_127
}
_127
};
_127
_127
export const storeSession = async (session: Session): Promise<void> => {
_127
vault.setValue('session', session);
_127
setState({ session });
_127
};
_127
_127
export const restoreSession = async (): Promise<Session | null> => {
_127
let session: Session | null = null;
_127
if (!(await vault.isEmpty())) {
_127
session = await vault.getValue<Session>('session');
_127
}
_127
setState({ session });
_127
return session;
_127
};
_127
_127
export const clearSession = async (): Promise<void> => {
_127
await vault.clear();
_127
setState({ session: null });
_127
};
_127
_127
export const sessionIsLocked = async (): Promise<boolean> => {
_127
return (
_127
vault.config?.type !== VaultType.SecureStorage &&
_127
vault.config?.type !== VaultType.InMemory &&
_127
!(await vault.isEmpty()) &&
_127
(await vault.isLocked())
_127
);
_127
};
_127
_127
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_127
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_127
_127
switch (await getAllowedMode(mode)) {
_127
case 'BiometricsWithPasscode': {
_127
newConfig.type = VaultType.DeviceSecurity;
_127
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_127
break;
_127
}
_127
case 'InMemory': {
_127
newConfig.type = VaultType.InMemory;
_127
newConfig.deviceSecurityType = DeviceSecurityType.None;
_127
break;
_127
}
_127
default: {
_127
newConfig.type = VaultType.SecureStorage;
_127
newConfig.deviceSecurityType = DeviceSecurityType.None;
_127
break;
_127
}
_127
}
_127
newConfig.lockAfterBackgrounded = newConfig.type === VaultType.InMemory ? 30000 : 2000;
_127
_127
await vault.updateConfig(newConfig);
_127
};
_127
_127
const getAllowedMode = async (mode: UnlockMode): Promise<UnlockMode> => {
_127
if (mode === 'BiometricsWithPasscode') {
_127
await provisionBiometrics();
_127
return (await Device.isBiometricsEnabled()) &&
_127
(await Device.isBiometricsAllowed()) !== BiometricPermissionState.Granted
_127
? 'InMemory'
_127
: 'BiometricsWithPasscode';
_127
}
_127
return mode;
_127
};
_127
_127
const provisionBiometrics = async (): Promise<void> => {
_127
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_127
try {
_127
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_127
} catch (error) {
_127
null;
_127
}
_127
}
_127
};
_127
_127
export const lockSession = async (): Promise<void> => {
_127
await vault.lock();
_127
setState({ session: null });
_127
};

Export the enhanceVault() function.

src/util/session-vault.ts
src/pages/Login.tsx

_127
import {
_127
BiometricPermissionState,
_127
BrowserVault,
_127
Device,
_127
Vault,
_127
VaultType,
_127
DeviceSecurityType,
_127
IdentityVaultConfig,
_127
} from '@ionic-enterprise/identity-vault';
_127
import { createVault } from './vault-factory';
_127
import { Session } from '../models/Session';
_127
import { setState } from './session-store';
_127
_127
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_127
_127
const vault: Vault | BrowserVault = createVault();
_127
_127
export const initializeVault = async (): Promise<void> => {
_127
try {
_127
await vault.initialize({
_127
key: 'io.ionic.gettingstartedivreact',
_127
type: VaultType.InMemory,
_127
deviceSecurityType: DeviceSecurityType.None,
_127
lockAfterBackgrounded: 30000,
_127
});
_127
} catch (e: unknown) {
_127
await vault.clear();
_127
await updateUnlockMode('SecureStorage');
_127
}
_127
_127
await enhanceVault();
_127
_127
vault.onLock(() => {
_127
setState({ session: null });
_127
});
_127
};
_127
_127
export const enhanceVault = async (): Promise<void> => {
_127
if (!(await vault.isEmpty())) {
_127
return;
_127
}
_127
_127
if (await Device.isSystemPasscodeSet()) {
_127
await updateUnlockMode('BiometricsWithPasscode');
_127
} else {
_127
await updateUnlockMode('InMemory');
_127
}
_127
};
_127
_127
export const storeSession = async (session: Session): Promise<void> => {
_127
vault.setValue('session', session);
_127
setState({ session });
_127
};
_127
_127
export const restoreSession = async (): Promise<Session | null> => {
_127
let session: Session | null = null;
_127
if (!(await vault.isEmpty())) {
_127
session = await vault.getValue<Session>('session');
_127
}
_127
setState({ session });
_127
return session;
_127
};
_127
_127
export const clearSession = async (): Promise<void> => {
_127
await vault.clear();
_127
setState({ session: null });
_127
};
_127
_127
export const sessionIsLocked = async (): Promise<boolean> => {
_127
return (
_127
vault.config?.type !== VaultType.SecureStorage &&
_127
vault.config?.type !== VaultType.InMemory &&
_127
!(await vault.isEmpty()) &&
_127
(await vault.isLocked())
_127
);
_127
};
_127
_127
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_127
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_127
_127
switch (await getAllowedMode(mode)) {
_127
case 'BiometricsWithPasscode': {
_127
newConfig.type = VaultType.DeviceSecurity;
_127
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_127
break;
_127
}
_127
case 'InMemory': {
_127
newConfig.type = VaultType.InMemory;
_127
newConfig.deviceSecurityType = DeviceSecurityType.None;
_127
break;
_127
}
_127
default: {
_127
newConfig.type = VaultType.SecureStorage;
_127
newConfig.deviceSecurityType = DeviceSecurityType.None;
_127
break;
_127
}
_127
}
_127
newConfig.lockAfterBackgrounded = newConfig.type === VaultType.InMemory ? 30000 : 2000;
_127
_127
await vault.updateConfig(newConfig);
_127
};
_127
_127
const getAllowedMode = async (mode: UnlockMode): Promise<UnlockMode> => {
_127
if (mode === 'BiometricsWithPasscode') {
_127
await provisionBiometrics();
_127
return (await Device.isBiometricsEnabled()) &&
_127
(await Device.isBiometricsAllowed()) !== BiometricPermissionState.Granted
_127
? 'InMemory'
_127
: 'BiometricsWithPasscode';
_127
}
_127
return mode;
_127
};
_127
_127
const provisionBiometrics = async (): Promise<void> => {
_127
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_127
try {
_127
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_127
} catch (error) {
_127
null;
_127
}
_127
}
_127
};
_127
_127
export const lockSession = async (): Promise<void> => {
_127
await vault.lock();
_127
setState({ session: null });
_127
};

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

src/util/session-vault.ts
src/pages/Login.tsx

_123
import {
_123
BiometricPermissionState,
_123
BrowserVault,
_123
Device,
_123
Vault,
_123
VaultType,
_123
DeviceSecurityType,
_123
IdentityVaultConfig,
_123
} from '@ionic-enterprise/identity-vault';
_123
import { createVault } from './vault-factory';
_123
import { Session } from '../models/Session';
_123
import { setState } from './session-store';
_123
_123
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_123
_123
const vault: Vault | BrowserVault = createVault();
_123
_123
export const initializeVault = async (): Promise<void> => {
_123
try {
_123
await vault.initialize({
_123
key: 'io.ionic.gettingstartedivreact',
_123
type: VaultType.InMemory,
_123
deviceSecurityType: DeviceSecurityType.None,
_123
lockAfterBackgrounded: 30000,
_123
});
_123
} catch (e: unknown) {
_123
await vault.clear();
_123
await updateUnlockMode('SecureStorage');
_123
}
_123
_123
await enhanceVault();
_123
_123
vault.onLock(() => {
_123
setState({ session: null });
_123
});
_123
};
_123
_123
export const enhanceVault = async (): Promise<void> => {
_123
if (await Device.isSystemPasscodeSet()) {
_123
await updateUnlockMode('BiometricsWithPasscode');
_123
} else {
_123
await updateUnlockMode('InMemory');
_123
}
_123
};
_123
_123
export const storeSession = async (session: Session): Promise<void> => {
_123
vault.setValue('session', session);
_123
setState({ session });
_123
};
_123
_123
export const restoreSession = async (): Promise<Session | null> => {
_123
let session: Session | null = null;
_123
if (!(await vault.isEmpty())) {
_123
session = await vault.getValue<Session>('session');
_123
}
_123
setState({ session });
_123
return session;
_123
};
_123
_123
export const clearSession = async (): Promise<void> => {
_123
await vault.clear();
_123
setState({ session: null });
_123
};
_123
_123
export const sessionIsLocked = async (): Promise<boolean> => {
_123
return (
_123
vault.config?.type !== VaultType.SecureStorage &&
_123
vault.config?.type !== VaultType.InMemory &&
_123
!(await vault.isEmpty()) &&
_123
(await vault.isLocked())
_123
);
_123
};
_123
_123
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_123
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_123
_123
switch (await getAllowedMode(mode)) {
_123
case 'BiometricsWithPasscode': {
_123
newConfig.type = VaultType.DeviceSecurity;
_123
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_123
break;
_123
}
_123
case 'InMemory': {
_123
newConfig.type = VaultType.InMemory;
_123
newConfig.deviceSecurityType = DeviceSecurityType.None;
_123
break;
_123
}
_123
default: {
_123
newConfig.type = VaultType.SecureStorage;
_123
newConfig.deviceSecurityType = DeviceSecurityType.None;
_123
break;
_123
}
_123
}
_123
newConfig.lockAfterBackgrounded = newConfig.type === VaultType.InMemory ? 30000 : 2000;
_123
_123
await vault.updateConfig(newConfig);
_123
};
_123
_123
const getAllowedMode = async (mode: UnlockMode): Promise<UnlockMode> => {
_123
if (mode === 'BiometricsWithPasscode') {
_123
await provisionBiometrics();
_123
return (await Device.isBiometricsEnabled()) &&
_123
(await Device.isBiometricsAllowed()) !== BiometricPermissionState.Granted
_123
? 'InMemory'
_123
: 'BiometricsWithPasscode';
_123
}
_123
return mode;
_123
};
_123
_123
const provisionBiometrics = async (): Promise<void> => {
_123
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_123
try {
_123
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_123
} catch (error) {
_123
null;
_123
}
_123
}
_123
};
_123
_123
export const lockSession = async (): Promise<void> => {
_123
await vault.lock();
_123
setState({ session: null });
_123
};

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

src/util/session-vault.ts
src/pages/Login.tsx

_123
import {
_123
BiometricPermissionState,
_123
BrowserVault,
_123
Device,
_123
Vault,
_123
VaultType,
_123
DeviceSecurityType,
_123
IdentityVaultConfig,
_123
} from '@ionic-enterprise/identity-vault';
_123
import { createVault } from './vault-factory';
_123
import { Session } from '../models/Session';
_123
import { setState } from './session-store';
_123
_123
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_123
_123
const vault: Vault | BrowserVault = createVault();
_123
_123
export const initializeVault = async (): Promise<void> => {
_123
try {
_123
await vault.initialize({
_123
key: 'io.ionic.gettingstartedivreact',
_123
type: VaultType.InMemory,
_123
deviceSecurityType: DeviceSecurityType.None,
_123
lockAfterBackgrounded: 30000,
_123
});
_123
} catch (e: unknown) {
_123
await vault.clear();
_123
await updateUnlockMode('SecureStorage');
_123
}
_123
_123
await enhanceVault();
_123
_123
vault.onLock(() => {
_123
setState({ session: null });
_123
});
_123
};
_123
_123
export const enhanceVault = async (): Promise<void> => {
_123
if (await Device.isSystemPasscodeSet()) {
_123
await updateUnlockMode('BiometricsWithPasscode');
_123
} else {
_123
await updateUnlockMode('InMemory');
_123
}
_123
};
_123
_123
export const storeSession = async (session: Session): Promise<void> => {
_123
vault.setValue('session', session);
_123
setState({ session });
_123
};
_123
_123
export const restoreSession = async (): Promise<Session | null> => {
_123
let session: Session | null = null;
_123
if (!(await vault.isEmpty())) {
_123
session = await vault.getValue<Session>('session');
_123
}
_123
setState({ session });
_123
return session;
_123
};
_123
_123
export const clearSession = async (): Promise<void> => {
_123
await vault.clear();
_123
setState({ session: null });
_123
};
_123
_123
export const sessionIsLocked = async (): Promise<boolean> => {
_123
return (
_123
vault.config?.type !== VaultType.SecureStorage &&
_123
vault.config?.type !== VaultType.InMemory &&
_123
!(await vault.isEmpty()) &&
_123
(await vault.isLocked())
_123
);
_123
};
_123
_123
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_123
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_123
_123
switch (await getAllowedMode(mode)) {
_123
case 'BiometricsWithPasscode': {
_123
newConfig.type = VaultType.DeviceSecurity;
_123
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_123
break;
_123
}
_123
case 'InMemory': {
_123
newConfig.type = VaultType.InMemory;
_123
newConfig.deviceSecurityType = DeviceSecurityType.None;
_123
break;
_123
}
_123
default: {
_123
newConfig.type = VaultType.SecureStorage;
_123
newConfig.deviceSecurityType = DeviceSecurityType.None;
_123
break;
_123
}
_123
}
_123
newConfig.lockAfterBackgrounded = newConfig.type === VaultType.InMemory ? 30000 : 2000;
_123
_123
await vault.updateConfig(newConfig);
_123
};
_123
_123
const getAllowedMode = async (mode: UnlockMode): Promise<UnlockMode> => {
_123
if (mode === 'BiometricsWithPasscode') {
_123
await provisionBiometrics();
_123
return (await Device.isBiometricsEnabled()) &&
_123
(await Device.isBiometricsAllowed()) !== BiometricPermissionState.Granted
_123
? 'InMemory'
_123
: 'BiometricsWithPasscode';
_123
}
_123
return mode;
_123
};
_123
_123
const provisionBiometrics = async (): Promise<void> => {
_123
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_123
try {
_123
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_123
} catch (error) {
_123
null;
_123
}
_123
}
_123
};
_123
_123
export const lockSession = async (): Promise<void> => {
_123
await vault.lock();
_123
setState({ session: null });
_123
};

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

src/util/session-vault.ts
src/pages/Login.tsx

_121
import {
_121
BiometricPermissionState,
_121
BrowserVault,
_121
Device,
_121
Vault,
_121
VaultType,
_121
DeviceSecurityType,
_121
IdentityVaultConfig,
_121
} from '@ionic-enterprise/identity-vault';
_121
import { createVault } from './vault-factory';
_121
import { Session } from '../models/Session';
_121
import { setState } from './session-store';
_121
_121
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_121
_121
const vault: Vault | BrowserVault = createVault();
_121
_121
export const initializeVault = async (): Promise<void> => {
_121
try {
_121
await vault.initialize({
_121
key: 'io.ionic.gettingstartedivreact',
_121
type: VaultType.InMemory,
_121
deviceSecurityType: DeviceSecurityType.None,
_121
lockAfterBackgrounded: 30000,
_121
});
_121
} catch (e: unknown) {
_121
await vault.clear();
_121
await updateUnlockMode('SecureStorage');
_121
}
_121
_121
vault.onLock(() => {
_121
setState({ session: null });
_121
});
_121
};
_121
_121
export const enhanceVault = async (): Promise<void> => {
_121
if (await Device.isSystemPasscodeSet()) {
_121
await updateUnlockMode('BiometricsWithPasscode');
_121
} else {
_121
await updateUnlockMode('InMemory');
_121
}
_121
};
_121
_121
export const storeSession = async (session: Session): Promise<void> => {
_121
vault.setValue('session', session);
_121
setState({ session });
_121
};
_121
_121
export const restoreSession = async (): Promise<Session | null> => {
_121
let session: Session | null = null;
_121
if (!(await vault.isEmpty())) {
_121
session = await vault.getValue<Session>('session');
_121
}
_121
setState({ session });
_121
return session;
_121
};
_121
_121
export const clearSession = async (): Promise<void> => {
_121
await vault.clear();
_121
setState({ session: null });
_121
};
_121
_121
export const sessionIsLocked = async (): Promise<boolean> => {
_121
return (
_121
vault.config?.type !== VaultType.SecureStorage &&
_121
vault.config?.type !== VaultType.InMemory &&
_121
!(await vault.isEmpty()) &&
_121
(await vault.isLocked())
_121
);
_121
};
_121
_121
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_121
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_121
_121
switch (await getAllowedMode(mode)) {
_121
case 'BiometricsWithPasscode': {
_121
newConfig.type = VaultType.DeviceSecurity;
_121
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_121
break;
_121
}
_121
case 'InMemory': {
_121
newConfig.type = VaultType.InMemory;
_121
newConfig.deviceSecurityType = DeviceSecurityType.None;
_121
break;
_121
}
_121
default: {
_121
newConfig.type = VaultType.SecureStorage;
_121
newConfig.deviceSecurityType = DeviceSecurityType.None;
_121
break;
_121
}
_121
}
_121
newConfig.lockAfterBackgrounded = newConfig.type === VaultType.InMemory ? 30000 : 2000;
_121
_121
await vault.updateConfig(newConfig);
_121
};
_121
_121
const getAllowedMode = async (mode: UnlockMode): Promise<UnlockMode> => {
_121
if (mode === 'BiometricsWithPasscode') {
_121
await provisionBiometrics();
_121
return (await Device.isBiometricsEnabled()) &&
_121
(await Device.isBiometricsAllowed()) !== BiometricPermissionState.Granted
_121
? 'InMemory'
_121
: 'BiometricsWithPasscode';
_121
}
_121
return mode;
_121
};
_121
_121
const provisionBiometrics = async (): Promise<void> => {
_121
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_121
try {
_121
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_121
} catch (error) {
_121
null;
_121
}
_121
}
_121
};
_121
_121
export const lockSession = async (): Promise<void> => {
_121
await vault.lock();
_121
setState({ session: null });
_121
};

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

src/util/session-vault.ts
src/pages/Login.tsx

_49
import {
_49
IonButton,
_49
IonContent,
_49
IonHeader,
_49
IonItem,
_49
IonLabel,
_49
IonList,
_49
IonPage,
_49
IonTitle,
_49
IonToolbar,
_49
} from '@ionic/react';
_49
import { useHistory } from 'react-router-dom';
_49
import { login } from '../util/authentication';
_49
_49
const Login = () => {
_49
const history = useHistory();
_49
_49
const loginClicked = async (): Promise<void> => {
_49
try {
_49
await login();
_49
history.replace('/tabs/tab1');
_49
} catch (error: unknown) {
_49
console.error('Failed to log in', error);
_49
}
_49
};
_49
_49
return (
_49
<IonPage>
_49
<IonHeader>
_49
<IonToolbar>
_49
<IonTitle>Login</IonTitle>
_49
</IonToolbar>
_49
</IonHeader>
_49
<IonContent>
_49
<IonList>
_49
<IonItem>
_49
<IonLabel>
_49
<IonButton expand="block" onClick={loginClicked}>
_49
Login
_49
</IonButton>
_49
</IonLabel>
_49
</IonItem>
_49
</IonList>
_49
</IonContent>
_49
</IonPage>
_49
);
_49
};
_49
_49
export default Login;

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

src/util/session-vault.ts
src/pages/Login.tsx

_51
import {
_51
IonButton,
_51
IonContent,
_51
IonHeader,
_51
IonItem,
_51
IonLabel,
_51
IonList,
_51
IonPage,
_51
IonTitle,
_51
IonToolbar,
_51
} from '@ionic/react';
_51
import { useHistory } from 'react-router-dom';
_51
import { login } from '../util/authentication';
_51
import { enhanceVault } from '../util/session-vault';
_51
_51
const Login = () => {
_51
const history = useHistory();
_51
_51
const loginClicked = async (): Promise<void> => {
_51
try {
_51
await login();
_51
await enhanceVault();
_51
history.replace('/tabs/tab1');
_51
} catch (error: unknown) {
_51
console.error('Failed to log in', error);
_51
}
_51
};
_51
_51
return (
_51
<IonPage>
_51
<IonHeader>
_51
<IonToolbar>
_51
<IonTitle>Login</IonTitle>
_51
</IonToolbar>
_51
</IonHeader>
_51
<IonContent>
_51
<IonList>
_51
<IonItem>
_51
<IonLabel>
_51
<IonButton expand="block" onClick={loginClicked}>
_51
Login
_51
</IonButton>
_51
</IonLabel>
_51
</IonItem>
_51
</IonList>
_51
</IonContent>
_51
</IonPage>
_51
);
_51
};
_51
_51
export default Login;

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/util/session-vault.ts
src/pages/Login.tsx

_127
import {
_127
BiometricPermissionState,
_127
BrowserVault,
_127
Device,
_127
Vault,
_127
VaultType,
_127
DeviceSecurityType,
_127
IdentityVaultConfig,
_127
} from '@ionic-enterprise/identity-vault';
_127
import { createVault } from './vault-factory';
_127
import { Session } from '../models/Session';
_127
import { setState } from './session-store';
_127
_127
export type UnlockMode = 'BiometricsWithPasscode' | 'InMemory' | 'SecureStorage';
_127
_127
const vault: Vault | BrowserVault = createVault();
_127
_127
export const initializeVault = async (): Promise<void> => {
_127
try {
_127
await vault.initialize({
_127
key: 'io.ionic.gettingstartedivreact',
_127
type: VaultType.InMemory,
_127
deviceSecurityType: DeviceSecurityType.None,
_127
lockAfterBackgrounded: 30000,
_127
});
_127
} catch (e: unknown) {
_127
await vault.clear();
_127
await updateUnlockMode('SecureStorage');
_127
}
_127
_127
await enhanceVault();
_127
_127
vault.onLock(() => {
_127
setState({ session: null });
_127
});
_127
};
_127
_127
export const enhanceVault = async (): Promise<void> => {
_127
if (!(await vault.isEmpty())) {
_127
return;
_127
}
_127
_127
if (await Device.isSystemPasscodeSet()) {
_127
await updateUnlockMode('BiometricsWithPasscode');
_127
} else {
_127
await updateUnlockMode('InMemory');
_127
}
_127
};
_127
_127
export const storeSession = async (session: Session): Promise<void> => {
_127
vault.setValue('session', session);
_127
setState({ session });
_127
};
_127
_127
export const restoreSession = async (): Promise<Session | null> => {
_127
let session: Session | null = null;
_127
if (!(await vault.isEmpty())) {
_127
session = await vault.getValue<Session>('session');
_127
}
_127
setState({ session });
_127
return session;
_127
};
_127
_127
export const clearSession = async (): Promise<void> => {
_127
await vault.clear();
_127
setState({ session: null });
_127
};
_127
_127
export const sessionIsLocked = async (): Promise<boolean> => {
_127
return (
_127
vault.config?.type !== VaultType.SecureStorage &&
_127
vault.config?.type !== VaultType.InMemory &&
_127
!(await vault.isEmpty()) &&
_127
(await vault.isLocked())
_127
);
_127
};
_127
_127
export const updateUnlockMode = async (mode: UnlockMode): Promise<void> => {
_127
const newConfig = { ...(vault.config as IdentityVaultConfig) };
_127
_127
switch (await getAllowedMode(mode)) {
_127
case 'BiometricsWithPasscode': {
_127
newConfig.type = VaultType.DeviceSecurity;
_127
newConfig.deviceSecurityType = DeviceSecurityType.Both;
_127
break;
_127
}
_127
case 'InMemory': {
_127
newConfig.type = VaultType.InMemory;
_127
newConfig.deviceSecurityType = DeviceSecurityType.None;
_127
break;
_127
}
_127
default: {
_127
newConfig.type = VaultType.SecureStorage;
_127
newConfig.deviceSecurityType = DeviceSecurityType.None;
_127
break;
_127
}
_127
}
_127
newConfig.lockAfterBackgrounded = newConfig.type === VaultType.InMemory ? 30000 : 2000;
_127
_127
await vault.updateConfig(newConfig);
_127
};
_127
_127
const getAllowedMode = async (mode: UnlockMode): Promise<UnlockMode> => {
_127
if (mode === 'BiometricsWithPasscode') {
_127
await provisionBiometrics();
_127
return (await Device.isBiometricsEnabled()) &&
_127
(await Device.isBiometricsAllowed()) !== BiometricPermissionState.Granted
_127
? 'InMemory'
_127
: 'BiometricsWithPasscode';
_127
}
_127
return mode;
_127
};
_127
_127
const provisionBiometrics = async (): Promise<void> => {
_127
if ((await Device.isBiometricsAllowed()) === BiometricPermissionState.Prompt) {
_127
try {
_127
await Device.showBiometricPrompt({ iosBiometricsLocalizedReason: 'Please authenticate to continue' });
_127
} catch (error) {
_127
null;
_127
}
_127
}
_127
};
_127
_127
export const lockSession = async (): Promise<void> => {
_127
await vault.lock();
_127
setState({ session: null });
_127
};

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 Tab1 page, 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/pages/Tab1.tsx

_106
import {
_106
IonButton,
_106
IonContent,
_106
IonHeader,
_106
IonItem,
_106
IonLabel,
_106
IonList,
_106
IonPage,
_106
IonTitle,
_106
IonToolbar,
_106
} from '@ionic/react';
_106
import { useHistory } from 'react-router';
_106
import { logout } from '../util/authentication';
_106
import { useSession } from '../util/session-store';
_106
(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.