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- Install the SDK.
- Render
<MfaSettings/>under your existingUsersProvideron 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.
- 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:
- A factor list, or Set up when no factor exists.
- A QR code rendered by
<QrCode/>and a copyable secret for manual entry. - A six-digit confirmation code. Scanning without confirming does not enroll the factor.
- Backup codes with copy and download actions. They appear once; regenerating a set invalidates the old one.
- 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
- 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.
- Sign out and sign in again. Confirm that the flow requests a second factor and accepts a valid authenticator code.
- 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
- Configure authentication for MFA policy fields.
- Client libraries for
<MfaSettings/>and<QrCode/>. - Authentication flows for custom challenge handling.
- Passkeys for another first or second factor.
- User management for audited operator recovery.