DEV Community

Robin for Capawesome

Posted on • Originally published at capawesome.io

Announcing the Capacitor Vault Plugin: Secrets Behind a Biometric Lock

Storing secrets on a mobile device sounds simple until you try to do it properly. You want them encrypted at rest, gated behind the user's biometrics or passcode, and you want that unlock to cover a whole session rather than firing a prompt on every read. That's exactly the gap the new Capacitor Vault plugin fills.

In this post, we'll look at why it exists, what it does, and how to use it.

Why Another Storage Plugin?

We already ship two related plugins:

  • Secure Preferences — encrypted key/value storage the app reads freely in the background.
  • Biometrics — on-demand biometric prompts.

Neither covers the "active lock + session" pattern. Secure Preferences is silent: there's no user-facing gate. Biometrics gives you a single prompt, but no notion of a session that stays unlocked across many reads and writes.

That session pattern is the one Ionic Identity Vault popularized — and it's being discontinued. The Capacitor Vault plugin fills that gap with a drop-in alternative, and adds first-class multi-vault support along the way.

What You Get

  • Active lock state — a single biometric or passcode prompt unlocks the vault, so you can read and write many values before it locks again.
  • Three vault typesBiometric, BiometricOrDevicePasscode, and DevicePasscode.
  • Auto-lock on background — configure how long the app can be backgrounded before the vault locks itself.
  • Lock and unlock events — with a trigger reason (MANUAL or TIMEOUT).
  • Multi-vault — independent vaults with their own keys and lock policies.
  • Hardware-backed encryption — keys live in the Android Keystore and iOS Keychain, with AES-256-GCM as the data cipher.
  • Key invalidation — a typed KEY_INVALIDATED error when the device's biometric set changes.
  • Export and import — a built-in migration API.
  • Typed error codes — branch on conditions like UNLOCK_CANCELED or BIOMETRY_NOT_AVAILABLE.

The plugin supports Capacitor 8 and above. A localStorage-backed web implementation is included for cross-platform development, but is clearly marked unsafe for production.

A Quick Tour

Initialize the Vault

Before anything else, initialize the vault once per session:

import { Vault, VaultType } from '@capawesome-team/capacitor-vault';

await Vault.initialize({
  type: VaultType.Biometric,
  title: 'Unlock vault',
  cancelButtonText: 'Cancel',
  iosFallbackButtonText: 'Use Passcode',
  lockAfterBackgrounded: 30000, // Use 0 to lock immediately on background
});
Enter fullscreen mode Exit fullscreen mode

Unlock the Vault

Calling unlock() triggers the platform's authentication prompt, and typed errors tell you exactly what happened:

import { Vault, ErrorCode } from '@capawesome-team/capacitor-vault';

try {
  await Vault.unlock();
} catch (error) {
  if (error.code === ErrorCode.UnlockCanceled) {
    // User dismissed the prompt
  } else if (error.code === ErrorCode.KeyInvalidated) {
    // Biometric set changed — re-enrollment required
  } else {
    throw error;
  }
}
Enter fullscreen mode Exit fullscreen mode

Read and Write Values

While the vault is unlocked, reads and writes behave like any key/value store:

import { Vault } from '@capawesome-team/capacitor-vault';

await Vault.setValue({ key: 'session_token', value: 'eyJhbGciOiJIUzI1NiIs...' });
const { value } = await Vault.getValue({ key: 'session_token' });
Enter fullscreen mode Exit fullscreen mode

Lock and Listen for Events

Lock manually with lock(), and subscribe to events to keep your UI in sync:

import { Vault } from '@capawesome-team/capacitor-vault';

await Vault.addListener('lock', ({ vaultId, trigger }) => {
  console.log(`Vault ${vaultId} locked (trigger: ${trigger}).`);
});
await Vault.addListener('unlock', ({ vaultId }) => {
  console.log(`Vault ${vaultId} unlocked.`);
});
Enter fullscreen mode Exit fullscreen mode

Multiple Vaults

Every method accepts an optional vaultId. Pass different identifiers and you get fully independent vaults — separate keys, separate lock state, separate prompts:

import { Vault, VaultType } from '@capawesome-team/capacitor-vault';

await Vault.initialize({ vaultId: 'alice', type: VaultType.Biometric, title: "Unlock Alice's vault" });
await Vault.initialize({ vaultId: 'bob', type: VaultType.BiometricOrDevicePasscode, title: "Unlock Bob's vault", lockAfterBackgrounded: 60000 });
Enter fullscreen mode Exit fullscreen mode

This is handy for multi-account apps, where each user's secrets live behind their own lock.

Vault, Secure Preferences, or SQLite?

The right choice depends on how the data is accessed:

  • SQLite — relational data with queries, joins, and indexes.
  • Secure Preferences — encrypted key/value the app reads freely in the background (OAuth refresh tokens, API keys).
  • Vault — encrypted key/value the user must actively unlock with biometrics or a passcode (password manager entries, TOTP secrets, app-lock screens).

Migrating from Ionic Identity Vault

If you're moving off Ionic Identity Vault, the exportData(...) and importData(...) methods give you a one-shot migration path.

Try It Out

The Capacitor Vault plugin is available now to all Capawesome Insiders. There's also an open-source demo app that walks through the full flow.

This article was originally published on the Capawesome blog. If you have questions or feedback, join us on Discord or subscribe to the newsletter.

Top comments (0)