Skip to content

Add two-factor authentication

Add authenticator enrollment, backup codes, and second-factor recovery to your app.

Add <MfaSettings/> to let signed-in users enroll an authenticator app, save backup codes, and manage their factors. The prebuilt sign-in screen then handles the second-factor challenge.

Before you start

  • Complete the Users quickstart and mount UsersProvider.
  • Install @lepsto/sdk. <MfaSettings/> is available from 0.3.0; use 1.0.0 or later for the current backend code-exchange setup.
  • Choose a settings page in your application. Users does not host this page.

Enrollment is available without enabling a separate product setting. Configure the requirement and recovery policy when you need them.

What your users get

Authenticator app: the user scans a QR code and proves enrollment with a six-digit TOTP code.

Backup codes: confirmation returns ten single-use codes by default. Show them once and ask the user to save them. The challenge screen shows how many remain so the user can replace the set before running out.

Passkeys: if enabled, an enrolled passkey appears among the available factors. It can answer a second-factor challenge. A passkey sign-in with device user verification already reaches two-factor assurance and skips that challenge. See Add passkeys.

Coverage across methods: a verified second factor gates password, email code, magic link, Google, and GitHub sign-ins. Password recovery cannot bypass it.

Assurance claims: successful second-factor authentication produces aal: 'aal2'. Your backend can check that claim on sensitive routes, as described in Sessions and tokens.

Drop the screen in

npm install @lepsto/sdk
  1. Install the SDK.
  2. Render <MfaSettings/> under your existing UsersProvider on the settings page:
import { SignedIn } from '@lepsto/sdk/users/react'
import { MfaSettings } from '@lepsto/sdk/users/react/ui'

export function SecuritySettings() {
  return (
    <SignedIn>
      <MfaSettings />
    </SignedIn>
  )
}

The component renders nothing for a signed-out user. SignedIn can also guard the surrounding layout. See Client libraries for provider setup.

  1. Optionally set the heading, card style, theme, and change handler. Replace the example product name and theme values with your own:
<MfaSettings
  heading="Two-step verification"
  chrome="none"
  theme={{ productName: 'Acme', accentColor: '#4f46e5', borderRadius: '8px' }}
  onChange={(factors) => console.log(factors.length)}
/>

Use chrome="none" when the settings page already supplies a card. onChange runs after confirmation, backup-code regeneration, or disabling a factor; use it to refresh related security information.

If you already use <UserProfile/>, its Security section includes this flow. Add a separate <MfaSettings/> only when you want it elsewhere.

What enrolment looks like

The component guides the user through these states:

  1. A factor list, or Set up when no factor exists.
  2. A QR code rendered by <QrCode/> and a copyable secret for manual entry.
  3. A six-digit confirmation code. Scanning without confirming does not enroll the factor.
  4. Backup codes with copy and download actions. They appear once; regenerating a set invalidates the old one.
  5. Actions to regenerate codes or disable a factor from the list.

Enrollment, regeneration, and disabling require fresh authentication. Once a factor exists, the prompt also requires second-factor proof. The component renders the step-up screen and resumes the action afterward, so this integration does not need a custom StepUpRequiredError handler.

The component explains policy refusals in the UI and leaves the factor in place. last_factor_required means the action would remove the last verified factor while MFA is required. last_way_in means the account would have no remaining sign-in method.

Verify it worked

  1. Sign in, open settings, and select Set up. Scan the QR code and confirm the six-digit code. Check that the factor appears and backup codes are displayed once.
  2. Sign out and sign in again. Confirm that the flow requests a second factor and accepts a valid authenticator code.
  3. Open Users → Users → <user> in the app. Check that MFA factors lists the authenticator and remaining backup-code count.

Testing the next sign-in checks that enrollment was confirmed and the factor is enforced.

Signing in afterwards

After the first factor passes, <SignIn/> shows the code field, backup-code option, and any available passkey. A custom form handles needs_second_factor and submits handle.submitSecondFactor(code). See Authentication flows and Client libraries.

When someone loses their phone

Start with a saved backup code. Submit it at the challenge in place of the authenticator code; it works once, and the UI updates the remaining count.

An enrolled passkey can also complete the challenge without a code.

If neither option is available, an operator can select Reset MFA in MFA factors on the user’s record.

Lepsto support cannot remove a user’s second factor. Recovery follows the product’s operator reset process.

Make it mandatory

Set mfa.required to enforce an assurance floor: an enrolled user cannot remove their last verified factor. Configure authentication lists this field, backup-code counts, reset delays, and failed-code limits.

Next steps

Was this page helpful?
Esc

Start typing to search the docs.

↑↓navigate↵select