> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-factor authentication

> The React SDK exposed MFA through a single useMfa hook and drove enrollment screens in the widget. In the JavaScript SDK each MFA operation is its own function and hook.

The React SDK grouped every MFA operation under a single `useMfa()` hook and rendered the enrollment, recovery-code, and verification screens inside the widget. The JavaScript SDK exposes each operation as its own function (and a matching React hook), so you build the TOTP enrollment, recovery-code, and device-management UI yourself.

Building that UI (QR rendering, code entry, device list, recovery-code screen) is the same in any JavaScript SDK app, so it isn't repeated here: [**MFA enrollment & management** (Building UI)](/docs/javascript/building-ui/mfa-enrollment-management) covers it with full code. This page is the React→JavaScript translation plus the migration-specific gotchas.

## What maps to what

| React SDK (`useMfa()`)                     | JavaScript SDK                                                       |
| ------------------------------------------ | -------------------------------------------------------------------- |
| `addDevice()`                              | `registerTotpMfaDevice()` / `useRegisterTotpMfaDevice()`             |
| `authenticateDevice({ code, deviceId })`   | `authenticateTotpMfaDevice()` / `useAuthenticateTotpMfaDevice()`     |
| `getUserDevices()`                         | `getMfaDevices()` / `useGetMfaDevices()`                             |
| `updateUserDevice(deviceId)`               | `setDefaultMfaDevice()` / `useSetDefaultMfaDevice()`                 |
| `deleteUserDevice(deviceId, mfaAuthToken)` | `deleteMfaDevice()` / `useDeleteMfaDevice()`                         |
| `getRecoveryCodes()`                       | `getMfaRecoveryCodes()` / `useGetMfaRecoveryCodes()`                 |
| `getNewRecoveryCodes()`                    | `createNewMfaRecoveryCodes()` / `useCreateNewMfaRecoveryCodes()`     |
| `authenticateRecoveryCode({ code })`       | `authenticateMfaRecoveryCode()` / `useAuthenticateMfaRecoveryCode()` |
| `completeAcknowledgement()`                | `acknowledgeRecoveryCodes()` / `useAcknowledgeRecoveryCodes()`       |
| `isPendingRecoveryCodesAcknowledgment()`   | `isPendingRecoveryCodesAcknowledgment()`                             |

## Migration-specific behavior

* **Register/remove need an elevated token the widget got for you.** `registerTotpMfaDevice()` and `deleteMfaDevice()` require an elevated access token (`TokenScope.Credentiallink` and `TokenScope.Credentialunlink`). The widget minted it automatically; now you obtain it by running a step-up re-verification — see [**Step-up authentication** (Building UI)](/docs/javascript/building-ui/step-up-authentication) for the full flow. The same pattern applies to `unlinkSocialAccount()` and passkey registration.
* **`deleteMfaDevice()`'s `mfaAuthToken` is optional.** Either pass an `mfaAuthToken` or hold an elevated `Credentialunlink` token from a step-up re-verification — supply one of the two. See [**Step-up authentication** (Building UI)](/docs/javascript/building-ui/step-up-authentication).
* **You show recovery codes now.** The widget revealed them automatically after enrollment; fetch with `getMfaRecoveryCodes()`, display once, then record acknowledgement with `acknowledgeRecoveryCodes()`. Use `isPendingRecoveryCodesAcknowledgment()` to know when that step is still owed.

## Errors you now own

| Error                                           | When                                                                                                    | Resolution                                                                                                                                                                                 |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `MfaInvalidOtpError`                            | The entered code is wrong or expired                                                                    | Ask the user to re-enter the current code from their app.                                                                                                                                  |
| `MfaRateLimitedError`                           | Too many attempts                                                                                       | Ask the user to wait before trying again.                                                                                                                                                  |
| `mfaAuthToken is required` (thrown client-side) | `deleteMfaDevice()` called with no elevated token and no `mfaAuthToken` — it asserts before any request | Re-verify with `requestedScopes: [TokenScope.Credentialunlink]`, then call `deleteMfaDevice({ deviceId })` — see [Step-up authentication](/docs/javascript/building-ui/step-up-authentication). |
| `403 Elevated access token required` on delete  | A request went out but the elevated token was missing the `Credentialunlink` scope                      | Re-verify with `requestedScopes: [TokenScope.Credentialunlink]` before retrying `deleteMfaDevice()` — see [Step-up authentication](/docs/javascript/building-ui/step-up-authentication).        |

## See also

* [MFA enrollment & management (Building UI)](/docs/javascript/building-ui/mfa-enrollment-management) — the full build with QR rendering and device UI
* Step-up authentication — require MFA before a sensitive action
* [Post-login user setup](/docs/javascript/migrating-from-react/post-login-user-setup) — prompt for MFA enrollment when it's required
