Skip to content

Two-Factor Authentication

CruzJS supports two-factor authentication (2FA) via TOTP authenticator apps (Google Authenticator, Authy) and SMS. The system includes trusted device management and backup codes for account recovery.

Register the TwoFactorModule in your application:

import { TwoFactorModule } from '@cruzjs/core/two-factor';
export default createCruzApp({
modules: [TwoFactorModule],
});
MethodHow It WorksRequirement
TOTPTime-based one-time passwords via authenticator appNone (built-in)
SMSOne-time code sent via text messageTwilio account

Call twoFactor.setupTOTP to generate a secret and QR code URI:

const { data } = trpc.twoFactor.setupTOTP.useMutation();
// data: { secret, qrCodeUri, backupCodes }

Show the QR code for the user to scan with their authenticator app:

function TwoFactorSetup() {
const setup = trpc.twoFactor.setupTOTP.useMutation();
return (
<div>
<button onClick={() => setup.mutate()}>Enable 2FA</button>
{setup.data && (
<div>
<QRCode value={setup.data.qrCodeUri} />
<p>Or enter this code manually: {setup.data.secret}</p>
</div>
)}
</div>
);
}

After scanning, the user enters a code from their authenticator app to confirm setup:

trpc.twoFactor.verifyTOTP.useMutation().mutate({
code: '123456',
});

Once verified, 2FA is active on the account.

const { data: status } = trpc.twoFactor.getStatus.useQuery();
// status: { enabled: true, method: 'totp', trustedDevices: [...] }

When a user with 2FA enabled logs in, the login flow returns a requiresTwoFactor: true flag instead of a session token. The user must then provide a TOTP code or backup code to complete authentication.

Backup codes are single-use recovery codes generated during 2FA setup. They allow access if the user loses their authenticator device.

const { data } = trpc.twoFactor.generateBackupCodes.useMutation();
// data: { codes: ['abc123', 'def456', 'ghi789', ...] }

Generate new backup codes at any time (invalidates previous codes):

trpc.twoFactor.generateBackupCodes.useMutation().mutate();

Users can mark a device as trusted to skip 2FA for 30 days. Trusted device tokens are stored in a cookie.

const { data } = trpc.twoFactor.listTrustedDevices.useQuery();
// data: [{ id, name, lastUsed, expiresAt }, ...]
trpc.twoFactor.removeTrustedDevice.useMutation().mutate({
deviceId: 'device_abc123',
});

Disabling requires a current TOTP code or a valid backup code to confirm identity:

trpc.twoFactor.disable.useMutation().mutate({
code: '123456', // TOTP code or backup code
});

SMS 2FA sends a one-time code via text message instead of using an authenticator app.

Set the following environment variables:

Terminal window
TWILIO_ACCOUNT_SID=ACxxxxx
TWILIO_AUTH_TOKEN=your-auth-token
TWILIO_PHONE_NUMBER=+15551234567
PlatformAdapter
CloudflareCloudflareTwilioTwoFactorAdapter
Docker / ContainersTwilioTwoFactorAdapter

The adapter is selected automatically based on your runtime adapter.

// 1. Enable SMS 2FA with phone number
trpc.twoFactor.setupSMS.useMutation().mutate({
phoneNumber: '+15559876543',
});
// 2. Verify the code sent via SMS
trpc.twoFactor.verifySMS.useMutation().mutate({
code: '123456',
});
ProcedureTypeDescription
twoFactor.getStatusqueryGet current 2FA status and trusted devices
twoFactor.setupTOTPmutationGenerate TOTP secret and QR code URI
twoFactor.verifyTOTPmutationVerify a TOTP code to activate 2FA
twoFactor.setupSMSmutationStart SMS 2FA setup with phone number
twoFactor.verifySMSmutationVerify SMS code to activate 2FA
twoFactor.generateBackupCodesmutationGenerate new backup codes
twoFactor.listTrustedDevicesqueryList trusted devices
twoFactor.removeTrustedDevicemutationRemove a trusted device
twoFactor.disablemutationDisable 2FA (requires code)