Add multi-factor authentication to your account with TOTP authenticator apps, passkeys, backup codes, and vault-level enforcement.
This guide walks you through every layer of multi-factor authentication (MFA) that Lifestream Vault supports.
By the end you will have:
MFA protection applies whenever you sign in. If vault-level MFA is active, it also triggers on a per-vault basis even within an already-authenticated session — giving you defence-in-depth for your most important data.
Prerequisites
TOTP (Time-based One-Time Password) is the most widely supported second factor. Your authenticator app generates a fresh 6-digit code every 30 seconds that you enter after your password.
Setup is a two-step process:
qrCodeUri (an otpauth:// URL) that you scan with your authenticator app.Via the UI: go to Settings → Security → Two-Factor Authentication → Add Authenticator App, scan the QR code, enter the 6-digit code, then click Enable.
We recommend using a cloud-syncing authenticator app like Authy, 1Password, or Bitwarden so your TOTP codes are available across all your devices. This prevents lockouts if you lose access to a single device.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
// Step 1 — log in to get an authenticated client
const { client } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
'you@example.com',
'your-password',
);
// Step 2 — start TOTP enrollment
const setup = await client.mfa.setupTotp();
// The qrCodeUri can be rendered as a QR code in your app
// or opened directly in an authenticator app
console.log('Scan this URI with your authenticator app:');
console.log(setup.qrCodeUri);
// otpauth://totp/Lifestream%20Vault:you%40example.com?secret=BASE32SECRET&issuer=Lifestream%20VaultWhen TOTP verification succeeds, the API returns a one-time list of backup codes. Each code can be used exactly once to sign in if you ever lose access to your authenticator app.
Backup codes look like this:
A7X9K2M5
Q3W8E1R6
B4N6P0L9
T2Y7J4H8
F5G1D0K3
V8C6X9N2
R4P7W1M6
L0S3E5A8
U9H2B7Z4
J6K8T3Q1
Store them in a password manager or print them and keep the printout in a physically secure place. You can regenerate codes at any time from Settings → Security → Backup Codes → Regenerate — regeneration invalidates all previous codes immediately.
To regenerate via the SDK:
// Regenerate backup codes (invalidates the previous set)
// Requires your current password as confirmation
const refreshed = await client.mfa.regenerateBackupCodes('your-password');
console.log('New backup codes (store these now!):');
refreshed.backupCodes.forEach((code, i) => {
console.log(` ${i + 1}. ${code}`);
});Store backup codes before closing this page. The API only returns codes once. If you lose both your authenticator app and your backup codes, account recovery requires contacting support and may involve identity verification. Treat backup codes like a password — never share them and do not store them in plaintext in a version-controlled repository.
Passkeys use the WebAuthn standard to let you authenticate with your device's biometrics (Face ID, Touch ID, Windows Hello) or a hardware security key (YubiKey, etc.). They are phishing-resistant and more convenient than TOTP because no code needs to be typed.
Passkey registration is a two-round-trip WebAuthn ceremony:
PublicKeyCredentialCreationOptions challenge.Via the UI: go to Settings → Security → Passkeys → Add Passkey, follow the browser prompt, then give the key a friendly name (e.g. MacBook Touch ID).
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
const { client } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
'you@example.com',
'your-password',
);
// Passkey registration requires the WebAuthn browser API —
// register passkeys through Settings → Security → Passkeys in the web UI.
// After registration, list your passkeys via the SDK:
const { passkeys } = await client.mfa.listPasskeys();
for (const pk of passkeys) {
console.log(`Passkey "${pk.name}" — added ${pk.createdAt}`);
}
// Rename a passkey
await client.mfa.renamePasskey(passkeys[0].id, 'MacBook Touch ID');Browser support: Passkeys work in Chrome 108+, Safari 16+, Firefox 122+, and Edge 108+. On mobile, iOS 16+ and Android 9+ support passkeys through iCloud Keychain and Google Password Manager respectively. For server-side applications where a browser is not available, TOTP is the recommended second factor.
Once MFA is enabled on your account, the standard login flow changes. Instead of receiving an access token immediately, the server returns a short-lived mfaToken after your password is verified. You must then complete the MFA challenge before the server issues a full access token.
The two-step flow:
1. POST /api/v1/auth/login { email, password }
→ 200 { mfaToken: "eyJ...", mfaRequired: true, mfaMethods: ["totp", ...] }
2. POST /api/v1/auth/mfa/totp { mfaToken, code } ← TOTP
POST /api/v1/auth/mfa/passkey/start { mfaToken } ← passkey (1/2)
POST /api/v1/auth/mfa/passkey/finish { mfaToken, ... } ← passkey (2/2)
POST /api/v1/auth/mfa/backup { mfaToken, code } ← backup code
→ 200 { accessToken: "eyJ...", user: { ... } }
The mfaToken is valid for 5 minutes. If it expires, the user must restart the login from step 1.
The Lifestream Vault SDK handles this automatically — when you call LifestreamVaultClient.login() with MFA-enabled credentials you can supply a mfaOptions object and the SDK performs both steps transparently.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
// The SDK completes the MFA challenge automatically when mfaOptions is provided
const { client, tokens } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
'you@example.com',
'your-password',
{}, // options (empty)
{
mfaCode: '482931', // current code from your authenticator app
},
);
console.log('Logged in successfully!');
console.log('Access token:', tokens.accessToken);Vault-level MFA enforcement requires a Pro or Business plan. Upgrade in Settings → Subscription. Account-level MFA (TOTP, passkeys, backup codes) is available on all tiers.
Even within an already-authenticated session, certain vaults can require a fresh MFA check before any document operations are permitted. This is enforced by the requireVaultMfa middleware on all document routes for vaults where it is enabled.
When vault-level MFA is active:
This feature is ideal for vaults containing sensitive data: financial records, credentials, legal documents, or anything you want to protect even from someone who has hijacked an active browser session.
Via the UI: open the vault → Settings → Security → Require MFA for this vault → Enable.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
const { client } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
'you@example.com',
'your-password',
{},
{ mfaCode: '482931' },
);
// Enable MFA enforcement on a specific vault
await client.vaults.setMfaConfig(vaultId, {
mfaRequired: true,
});
console.log('Vault MFA enforcement is now active.');Over time you may need to rename a passkey, remove an old authenticator, or disable MFA entirely. All management operations are available in Settings → Security and via the SDK and CLI.
Listing enrolled MFA methods:
Each MFA method has a type (totp, passkey, backup_code) and, for passkeys, a user-assigned name and a createdAt timestamp.
// List registered passkeys
const { passkeys } = await client.mfa.listPasskeys();
console.log('Passkeys registered:', passkeys.length);
passkeys.forEach((pk) => {
console.log(`Passkey "${pk.name}" — added ${pk.createdAt}`);
});
// Remove a passkey by ID (does not disable TOTP)
await client.mfa.deletePasskey(passkeyId);
console.log('Passkey removed.');
// Disable TOTP entirely (requires current password as confirmation)
await client.mfa.disableTotp('your-password');
console.log('TOTP disabled.');Before removing your last MFA method, make sure you have backup codes available. Disabling all MFA methods reverts your account to password-only authentication. If your account is subject to an organisational policy that requires MFA, disabling it may lock you out until an admin re-enables access.
| Factor | Passkey | TOTP |
|---|---|---|
| Phishing resistance | Excellent — the credential is domain-bound | Moderate — codes can be intercepted |
| Convenience | One biometric tap | Must read and type a 6-digit code |
| Works offline | Yes (device-local) | Yes (time-based, no network needed) |
| Shared devices | Not ideal (tied to authenticator) | Fine with any app |
| Recovery | Requires hardware or cloud backup | Backup codes cover loss |
Recommendation: register at least one passkey and keep TOTP enrolled as a fallback. That way, if a platform update temporarily breaks passkeys, you still have a code-based route in.
Enable vault-level MFA enforcement for vaults that hold:
The 8-hour TTL means the prompt only appears once per work session — it adds seconds of friction in exchange for a significant security improvement.
If you lose both your authenticator app and your backup codes:
To avoid this scenario, keep backup codes in at least two separate locations.
Your account is now protected by multiple layers of authentication. Here are some natural next steps: