Secure Your Account with MFA

Add multi-factor authentication to your account with TOTP authenticator apps, passkeys, backup codes, and vault-level enforcement.

beginner12 min read

What You'll Build

This guide walks you through every layer of multi-factor authentication (MFA) that Lifestream Vault supports.

By the end you will have:

  • A TOTP authenticator app (Google Authenticator, Authy, 1Password, etc.) enrolled on your account
  • A set of backup codes stored somewhere safe for account recovery
  • Optionally, a passkey (biometric or hardware key) registered as a second factor
  • Optionally, vault-level MFA enforcement so that sensitive vaults require a fresh MFA check before access is granted

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

  • A Lifestream Vault account (any tier)
  • Node.js 18+ if using the SDK or CLI
  • An authenticator app installed on your phone (for TOTP setup)
  • Pro tier or higher to enable vault-level MFA enforcement

Set Up TOTP (Authenticator App)

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:

  1. Initiate setup — the API returns a qrCodeUri (an otpauth:// URL) that you scan with your authenticator app.
  2. Verify — submit one code from the app to confirm enrollment. The server verifies the code and, on success, returns your backup codes.

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.

typescript
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%20Vault

Save Your Backup Codes

When 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:

typescript
// 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.

Register a Passkey

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:

  1. Start — the server returns a PublicKeyCredentialCreationOptions challenge.
  2. Finish — the browser (or native authenticator) signs the challenge; you send the credential back to the server.

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).

typescript
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.

The MFA Login Flow

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.

typescript
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

Plan Required

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:

  • Reading, writing, and deleting documents inside the vault all require a valid recent MFA assertion
  • The assertion is stored server-side with a configurable TTL (default: 8 hours)
  • After the TTL expires the user must complete a fresh MFA check before the vault becomes accessible again
  • API keys bypass vault MFA because they carry explicit scope — only interactive JWT sessions are affected

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.

typescript
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.');

Manage MFA Devices

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.

typescript
// 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.

Tips & Best Practices

Passkey vs. TOTP: Which Should You Use?

FactorPasskeyTOTP
Phishing resistanceExcellent — the credential is domain-boundModerate — codes can be intercepted
ConvenienceOne biometric tapMust read and type a 6-digit code
Works offlineYes (device-local)Yes (time-based, no network needed)
Shared devicesNot ideal (tied to authenticator)Fine with any app
RecoveryRequires hardware or cloud backupBackup 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.

Backup Code Storage

  • Save codes in your password manager (1Password, Bitwarden, etc.) as a secure note
  • Or print them and store the paper in a fireproof safe
  • Never commit them to a git repository or paste them into a chat window
  • Regenerate codes after using any of them, or if you suspect exposure

Vault-Level MFA for Sensitive Data

Enable vault-level MFA enforcement for vaults that hold:

  • Financial records or tax documents
  • API credentials and secrets
  • Legal contracts or PII
  • Anything subject to compliance requirements (HIPAA, GDPR, SOC 2)

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.

Recovery Procedure

If you lose both your authenticator app and your backup codes:

  1. Use a registered passkey if one is available on another device or in iCloud/Google Keychain
  2. Contact Lifestream Vault support with account verification information
  3. Support can issue a one-time recovery code after identity verification

To avoid this scenario, keep backup codes in at least two separate locations.

What's Next

Your account is now protected by multiple layers of authentication. Here are some natural next steps:

  • Set Up SSO for Your Organization — configure SAML single sign-on with Okta, Azure AD, or OneLogin for your whole team (Business tier)
  • Self-hosters can review security settings in the Configuration Guide.
  • Additional security options are available in Settings → Security.
  • API keys are managed in Settings → API Keys.