Two-Factor Setup

Scan, verify, save recovery codes. The recovery step is a step, not a footnote: an authenticator lost with no codes saved is an account lost, and burying them behind a link is why that happens.

src/components/ui/two-factor-setup.tsx

About

From the component's own source, where it was written.

Enrolling an authenticator app, end to end.

Recovery codes come last and the flow does not finish until they have been seen. Enabling 2FA without them is how people lock themselves out of their own accounts permanently — the support cost of skipping this step is larger than the cost of the step.

The secret is shown alongside the QR code, not instead of it. Desktop password managers cannot scan a screen, and a QR-only flow forces people onto a second device they may not have.

Install

npx astralyx-ui add two-factor-setup

Usage

import { TwoFactorSetup } from '@/components/ui/two-factor-setup'

<TwoFactorSetup step={step} onStepChange={setStep} secret={secret} recoveryCodes={codes} />

Composer

Change the props and the source below follows.

Two-factor authentication
  1. In progress
    Scan
  2. 2
    Verify
  3. 3
    Recovery codes

Scan this with your authenticator app.

QR code
Or enter this keyJBSWY3DPEHPK3PXPAstralyx · a.okafor@example.com
Props
tsx
<TwoFactorSetup
  step="scan"
  onStepChange={setStep}
  secret={secret}
  recoveryCodes={codes}
/>

Examples

Full enrolment flow

API

Generated from the source, so it cannot fall behind the code. One table per exported component, then hooks, functions and types.

TwoFactorSetup

Extends Omit<ComponentProps<'div'>, 'children' | 'title'>.

PropTypeDefaultDescription
stepTwoFactorSteprequired'scan' | 'verify' | 'recovery' | 'done'. Controlled, because enrolment usually spans a server round-trip.
onStepChange(step: TwoFactorStep) => void——
qrReactNode—Your rendered QR. The kit does not ship a QR encoder for one screen.
secretstring—Base32, shown for manual entry with a copy button. A QR code is unreadable on the device displaying it.
recoveryCodesstring[]—Shown once, with copy and download. The step cannot be completed without an explicit acknowledgement that they are saved.
onVerify(code: string) => void—Called with the six digits. Advancing is yours to decide — the component never assumes success.
onFinish() => void——
errorReactNode——
issuerstring'Astralyx'—
accountReactNode——
titleReactNode'Two-factor authentication'—
stepLabels{ scan: ReactNode; verify: ReactNode; recovery: ReactNode }{ scan: 'Scan', verify: 'Verify', recovery: 'Recovery codes' }Names of the three progress steps.
scanInstructionReactNode'Scan this with your authenticator app.'—
qrPlaceholderReactNode'QR code'Stand-in shown when no qr is supplied.
secretLabelReactNode'Or enter this key'—
copySecretLabelstring'Copy setup key'—
continueLabelReactNode'Continue'—
verifyInstructionReactNode'Enter the six-digit code from your app.'—
codeLabelstring'Verification code'Accessible name for the six-digit field.
verifyLabelReactNode'Verify'—
backLabelReactNode'Back'—
recoveryWarningReactNode'Save these somewhere safe. Each one works once, and they are the only way back in if you lose the device.'—
copyCodesLabelstring'Copy recovery codes'—
savedLabelReactNode'I have saved these codes'—
finishLabelReactNode'Finish'—

Types

TypeDefinitionDescription
TwoFactorStep
'scan' | 'verify' | 'recovery' | 'done'
Enrolling an authenticator app, end to end.