Better Auth in Rust

Two-Factor Authentication

TOTP, OTP, and backup codes for two-factor authentication.

The TwoFactorPlugin adds two-factor authentication (2FA) to your application. It supports TOTP (authenticator apps), OTP (email-based one-time passwords), and backup codes for recovery.

Setup

These examples assume you've already defined AppAuthSchema. If you want a complete setup from entity definitions through Axum mounting, start with the Axum integration guide.

use better_auth::plugins::TwoFactorPlugin;

let auth = BetterAuth::<AppAuthSchema>::new(config)
    .store(store)
    .plugin(TwoFactorPlugin::new())
    .build()
    .await?;

Configuration

use better_auth::plugins::two_factor::TwoFactorConfig;
use std::sync::Arc;

let auth = BetterAuth::<AppAuthSchema>::new(config)
    .store(store)
    .plugin(
        TwoFactorPlugin::new().with_config(TwoFactorConfig {
            issuer: Some("My App".to_string()),
            skip_verification_on_enable: false,
            two_factor_cookie_max_age: 600,
            trust_device_max_age: 30 * 24 * 60 * 60,
            totp_period: 30,
            totp_digits: 6,
            send_otp: None,
        })
    )
    .build()
    .await?;
OptionTypeDefaultDescription
issuerOption<String>NoneIssuer name shown in authenticator apps. Falls back to app_name.
skip_verification_on_enableboolfalseMarks the user as 2FA-enabled immediately during enrollment.
two_factor_cookie_max_agei64600Lifetime of the pending 2FA cookie in seconds.
trust_device_max_agei642592000Lifetime of the trusted-device cookie in seconds.
totp_periodu6430TOTP time step in seconds
totp_digitsusize6Number of digits in TOTP code
send_otpOption<Arc<dyn SendTwoFactorOtp>>NoneCallback used by /two-factor/send-otp.

How It Works

Enrollment Flow

  1. User calls /two-factor/enable with their password
  2. An encrypted TOTP secret is generated and stored
  3. Backup codes are generated and stored as encrypted JSON
  4. User scans the TOTP URI in their authenticator app
  5. twoFactorEnabled remains false until verification unless skip_verification_on_enable is enabled

Sign-In Flow with 2FA

  1. User signs in with email/password as normal
  2. If 2FA is enabled, instead of creating a session, a pending verification is created
  3. User must verify with one of:
    • TOTP: Code from authenticator app
    • OTP: Code sent via email
    • Backup code: One-time recovery code
  4. After successful verification, a session is created

API Endpoints

The 2FA plugin exposes the following endpoints. For full request/response details, see the OpenAPI Reference.

EndpointMethodDescription
/two-factor/enablePOSTEnable 2FA (returns TOTP URI and backup codes)
/two-factor/disablePOSTDisable 2FA (requires password)
/two-factor/get-totp-uriPOSTRetrieve TOTP URI for enrolled user
/two-factor/verify-totpPOSTVerify TOTP code during sign-in
/two-factor/send-otpPOSTSend OTP via email
/two-factor/verify-otpPOSTVerify email OTP
/two-factor/generate-backup-codesPOSTGenerate new backup codes
/two-factor/verify-backup-codePOSTVerify a backup code during sign-in

Backup codes are shown only once during enrollment or regeneration on the public HTTP surface. Store them securely.

For TS compatibility, Rust keeps backup-code retrieval as a server-only capability. Use TwoFactorPlugin::view_backup_codes(...) from trusted server code when you need to read the currently stored codes. There is no public HTTP route for this.

Security Details

  • TOTP secrets are encrypted before being stored in the database
  • Backup codes are stored as encrypted JSON — the plaintext is only returned once during generation
  • OTP codes are stored as verification records and expire after 3 minutes
  • Password verification is required to enable/disable 2FA and to regenerate backup codes
  • Used backup codes are immediately removed from the stored set

Errors

StatusCondition
400Invalid password
400TOTP not enabled
400Two factor isn't enabled
400OTP has expired
400Too many attempts. Please request a new code.
401Invalid code
401Invalid backup code
401Invalid two factor cookie
401Unauthenticated (missing or invalid session)

On this page