Docs

Two-Factor Authentication (2FA)

OTP TOTP Backup Codes Trusted Devices

Two-Factor Authentication (2FA) adds an extra security step when users log in. Instead of just using a password, they'll need to provide a second form of verification. This makes it much harder for unauthorized people to access accounts, even if they've somehow gotten the password.

This plugin offers two main methods to do a second factor verification:

  1. OTP (One-Time Password): A temporary code sent to the user's email or phone.
  2. TOTP (Time-based One-Time Password): A code generated by an app on the user's device.

Additional features include:

  • Generating backup codes for account recovery
  • Enabling/disabling 2FA
  • Managing trusted devices

Installation

Add the plugin to your auth config

Add the two-factor plugin to your auth configuration and specify your app name as the issuer.

auth.ts
import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins"
 
export const auth = await betterAuth({
    // ... other config options
    plugins: [
        twoFactor({  
            issuer: "my-app"
        }) 
    ]
})

Migrate your database:

Run the migration to add the required fields to the user table.

See the schema-section to see what fields this plugin requires.

npx better-auth migrate

Add the client Plugin

Add the client plugin and Specify where the user should be redirected if they need to verify 2nd factor

client.ts
import { createAuthClient } from "better-auth/client"
import { twoFactorClient } from "better-auth/client/plugins"
 
const client = createAuthClient({
    plugins: [
        twoFactorClient({ 
            twoFactorPage: "/two-factor" //redirect for two factor verification if enabled
        })
    ]
})

Usage

Enabling 2FA

To enable two-factor for a user you need to call twoFactor.enable with the user password.

two-factor.ts
const enableTwoFactor = async() => {
    await client.twoFactor.enable({
        password: "password" //the user password must be provided
    })
}

When a user enables two-factor authentication, 2 things happen:

  • An encrypted twoFactorSecret and twoFactorBackupCodes are generated.
  • twoFactorEnabled is set to true

Sign In with 2FA

When a user with 2FA enabled tries to sign in via email, they will be redirected to the twoFactorPage (specified in your client config) unless they're using a trusted device.

sign-in.ts
const signin = async () => {
    await client.signIn.email({
        email: "[email protected]",
        password: "password123",
    })
}

By default, if a user has 2FA enabled, they will be redirected to the twoFactorPage. If you want to manage the 2FA verification directly within your application instead, simply pass redirect:false to the client plugin and handle the verification in the callback.

sing-in.ts
import { client } from "better-auth/client";
import { twoFactorClient } from "better-auth/client/plugins";
 
const client = createAuthClient({
    plugins: [twoFactorClient({
        redirect: false
    })]
})
 
const signin = async () => {
    await client.signIn.email({
        email: "[email protected]",
        password: "password123",
    }, {
         async onSuccess(context) {
                if (context.data.twoFactorRedirect) {
                    // Handle the 2FA verification in place
                }
        }
    })
}

TOTP

TOTP is a time-based one-time password algorithm that generates a code based on the current time.

Getting TOTP URI

After enabling 2FA, you can get the TOTP URI to display to the user.

const { data, error } = await client.twoFactor.getTotpUri()
if (data) {
    // Use data.totpURI to generate a QR code or display to the user
}

Example: Using React

user-card.tsx
import QRCode from "react-qr-code";
 
export default function UserCard(){
    const { data: session } = client.useSession();
	const { data: qr } = useQuery({
		queryKey: ["two-factor-qr"],
		queryFn: async () => {
			const res = await client.twoFactor.getTotpUri();
			return res.data;
		},
		enabled: !!session?.user.twoFactorEnabled,
	});
    return (
        <QRCode value={qr?.totpURI || ""} />
   )
}

Verifying TOTP

After the user has entered their 2FA code, you can verify it usinng twoFacotr.verifyTotp method.

const verifyTotp = async (code: string) => {
    const { data, error } = await client.twoFactor.verifyTotp({ code })
}

OTP

OTP (One-Time Password) is similar to TOTP but the code is sent directly to the user, and the code is valid for 3 mins by default.

Before using OTP to verify the second factor, you need to configure sendOTP in your better auth instance. This function is responsible for sending the OTP to the user's email, phone, or any other method supported by your application.

auth.ts
import {  } from "better-auth"
import {  } from "better-auth/plugins"
 
export const  = await ({
    : {
        : "sqlite",
        : "./db.sqlite",
    },
    : [
        ({
          	: {
				async (, ) {
                    // send otp to user
				},
			},
        })
    ]
})

Sending OTP

Sending an OTP is done by calling the twoFactor.sendOtp function. This function will trigger your sendOTP implementation that you provided in the Better Auth configuration.

const { data, error } = await client.twoFactor.sendOtp()
if (data) {
    // redirect or show the user to enter the code
}

Verifying OTP

After the user has entered their OTP code, you can verify it

const verifyOtp = async (code: string) => {
    await client.twoFactor.verifyOtp({ code }, {
        onSuccess(){
            //redirect the user on success
        },
        onError(ctx){
            alert(ctx.error.message)
        }
    })
}

Backup Codes

Backup codes are generated and stored in the database. This can be used to recover access to the account if the user loses access to their phone or email.

Generating Backup Codes

Generate backup codes for account recovery:

const { data, error } = await client.twoFactor.generateBackupCodes()
if (data) {
    // Show the backup codes to the user
}

Using Backup Codes

You can now allow users to provider backup code as account recover method.

await client.twoFactor.verifyBackupCode({code: ""}, {
    onSuccess(){
        //redirect the user on success
    },
    onError(ctx){
        alert(ctx.error.message)
    }
})

Trusted Devices

You can mark a device as trusted by passing trustDevice to verifyTotp or verifyOtp.

const verify2FA = async (code: string) => {
    const { data, error } = await client.twoFactor.verifyTotp({
        code,
        callbackURL: "/dashboard",
        trustDevice: true // Mark this device as trusted
    })
    if (data) {
        // 2FA verified and device trusted
    }
}

When trustDevice is set to true, the current device will be remembered for 60 days. During this period, the user won't be prompted for 2FA on subsequent sign-ins from this device. The trust period is refreshed each time the user signs in successfully.

Schema

The plugin requires 3 additional fields in the user table.

  • twoFactorEnabled: (boolean) - a boolean value that will be set true or false when authenticated user enable or disable two factor.
  • twoFactorSecret: (string) - Encrypted secret used to generate totp and otp.
  • twoFactorBackupCodes: (string) - Encrypted list of backup codes stored as a string separted by comma.

Options

Server

Issuer: The issuer is the name of your application. It's used to generate totp codes. It'll be displayed in the authenticator apps.

TOTP options

these are options for TOTP.

PropTypeDefault
digits
number
6
period
number
30

OTP options

these are options for OTP.

PropTypeDefault
sendOTP
function
-
period
number
30

Backup Code Options

backup codes are generated and stored in the database when the user enabled two factor authentication. This can be used to recover access to the account if the user loses access to their phone or email.

PropTypeDefault
amount
number
10
length
number
10
customBackupCodesGenerate
function
-

Client

To use the two factor plugin in the client, you need to add it on your plugins list.

client.ts
import {  } from "better-auth/client"
import {  } from "better-auth/client/plugins"
 
const  = ({
    : [
        ({ 
            : "/two-factor"
        }) 
    ] 
})

Options

twoFactorPage: The page to redirect the user to after they have enabled 2-Factor. This is the page where the user will be redirected to verify their 2-Factor code.

redirect: If set to false, the user will not be redirected to the twoFactorPage after they have enabled 2-Factor.

On this page

Edit on GitHub