Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.dynamic.xyz/docs/llms.txt

Use this file to discover all available pages before exploring further.

Authenticator apps provide a time-based one-time password (TOTP) that can be used for authentication.

Prerequisites

  • You need to have the Dynamic Client initialized.
  • You need to have the authenticator apps MFA enabled in your environment’s settings in the Dynamic dashboard.

Registering a new TOTP device

Calling registerTotpMfaDevice will return a URI and a secret key for the TOTP device. You can use the URI to display a QR code to the user, so they can scan it with their authenticator app to get a TOTP code. Unlike passkeys, registering a TOTP device will not automatically authenticate it, so you need to call authenticateTotpMfaDevice after registering a TOTP device for the user to complete an MFA challenge.
import { registerTotpMfaDevice } from '@dynamic-labs-sdk/client';

const register = async () => {
  const { uri, secret } = await registerTotpMfaDevice();
  console.log(uri, secret);
};

Doing MFA authentication with a TOTP code

Calling authenticateTotpMfaDevice will verify the TOTP code and complete the MFA challenge. The authentication will be successful if the user enters a valid TOTP code for the registered TOTP device.
import { authenticateTotpMfaDevice } from '@dynamic-labs-sdk/client';

const onLogin = async () => {
  await authenticateTotpMfaDevice({ code: '123456' });
};
Pass requestedScopes to receive an elevated access token for sensitive actions:
import { authenticateTotpMfaDevice } from '@dynamic-labs-sdk/client';

const onExportPrivateKeyClick = async () => {
  await authenticateTotpMfaDevice({
    code: '123456',
    requestedScopes: ['wallet:export'],
  });

  // Elevated token is now stored — the SDK attaches it automatically
  await exportWaasPrivateKey(params);
};

With MFA token (deprecated)

createMfaTokenOptions is deprecated and will be removed in the next major version. Use requestedScopes instead. See migration guide.
import { authenticateTotpMfaDevice } from '@dynamic-labs-sdk/client';

const onExportPrivateKeyClick = async () => {
  await authenticateTotpMfaDevice({
    code: '123456',
    createMfaTokenOptions: { singleUse: true },
  });

  await exportWaasPrivateKey(params);
};

Deleting a TOTP device

Calling deleteMfaDevice will delete a TOTP device associated with the authenticated user. To delete a TOTP device, you first need to get the user to perfome an authentication challenge with that TOTP device, and then use the deleteMfaDevice function with that MFA token.
import { deleteMfaDevice } from '@dynamic-labs-sdk/client';

const delete = async () => {
  // Authenticate with the TOTP device to be deleted
  // Replace '123456' with the actual TOTP code the user enters
  await authenticateTotpMfaDevice({
    code: '123456',
    createMfaTokenOptions: { singleUse: true },
  });

  // Get the MFA token from the dynamic client
  const mfaToken = dynamicClient.mfaToken;

  // Replace 'device-id' with the actual ID of the TOTP device you want to delete
  // Replace 'mfa-auth-token' with the actual MFA authentication token
  await deleteMfaDevice({ deviceId: 'device-id', mfaAuthToken: mfaToken });
};

Getting all registered TOTP devices for a user

Calling getMfaDevices will return all registered TOTP devices for the authenticated user. Currently, only one TOTP device is supported per user.
import { getMfaDevices } from '@dynamic-labs-sdk/client';

const getUserMfaDevices = async () => {
  const mfaDevices = await getMfaDevices();
  console.log(mfaDevices);
};

React

The TOTP flow requires multi-step state: first register the device (get the URI/QR code), then verify with a code:
import { registerTotpMfaDevice, authenticateTotpMfaDevice } from '@dynamic-labs-sdk/client';
import { useState } from 'react';

function TotpEnrollment() {
  const [qrUri, setQrUri] = useState('');
  const [code, setCode] = useState('');
  const [enrolled, setEnrolled] = useState(false);

  const handleRegister = async () => {
    const { uri } = await registerTotpMfaDevice();
    setQrUri(uri);
  };

  const handleVerify = async (e) => {
    e.preventDefault();
    await authenticateTotpMfaDevice({ code });
    setEnrolled(true);
  };

  if (enrolled) return <p>TOTP device enrolled successfully.</p>;

  return (
    <div>
      {!qrUri ? (
        <button onClick={handleRegister}>Set Up Authenticator App</button>
      ) : (
        <form onSubmit={handleVerify}>
          <p>Scan this QR code with your authenticator app:</p>
          {/* render QR code from qrUri using a QR library */}
          <input
            value={code}
            onChange={(e) => setCode(e.target.value)}
            placeholder="Enter 6-digit code"
            maxLength={6}
          />
          <button type="submit">Verify</button>
        </form>
      )}
    </div>
  );
}