|
|
|
|
@@ -5,11 +5,9 @@ description: Enhance your app's security with two-factor authentication
|
|
|
|
|
|
|
|
|
|
`OTP` `TOTP` `Backup Codes` `Trusted Devices`
|
|
|
|
|
|
|
|
|
|
## What is Two-Factor Authentication?
|
|
|
|
|
|
|
|
|
|
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 of 2FA:
|
|
|
|
|
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.
|
|
|
|
|
@@ -46,11 +44,7 @@ This plugin offers two main methods of 2FA:
|
|
|
|
|
### Migrate your database:
|
|
|
|
|
Run the migration to add the required fields to the user table.
|
|
|
|
|
|
|
|
|
|
this will add the following fields to the **user** table:
|
|
|
|
|
|
|
|
|
|
- `twoFactorEnabled`: A boolean field to indicate if 2FA is enabled for the user.
|
|
|
|
|
- `twoFactorSecret`: The secret key used to generate TOTP codes.
|
|
|
|
|
- `twoFactorBackupCodes`: Encrypted backup codes for account recovery.
|
|
|
|
|
See the [schema-section](#schema) to see what fields this plugin requires.
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
npx better-auth migrate
|
|
|
|
|
@@ -60,7 +54,7 @@ This plugin offers two main methods of 2FA:
|
|
|
|
|
<Step>
|
|
|
|
|
### Add the client Plugin
|
|
|
|
|
|
|
|
|
|
Add the client plugin and Specify where to redirect the user after enabling 2FA:
|
|
|
|
|
Add the client plugin and Specify where the user should be redirected if they need to verify 2nd factor
|
|
|
|
|
|
|
|
|
|
```ts title="client.ts"
|
|
|
|
|
import { createAuthClient } from "better-auth/client"
|
|
|
|
|
@@ -81,44 +75,56 @@ This plugin offers two main methods of 2FA:
|
|
|
|
|
|
|
|
|
|
### Enabling 2FA
|
|
|
|
|
|
|
|
|
|
To enable two-factor authentication for a user:
|
|
|
|
|
To enable two-factor for a user you need to call `twoFactor.enable` with the user `password`.
|
|
|
|
|
|
|
|
|
|
```ts title="two-factor.ts"
|
|
|
|
|
const enableTwoFactor = async() => {
|
|
|
|
|
const { data, error } = await client.twoFactor.enable()
|
|
|
|
|
if (data) {
|
|
|
|
|
// 2FA has been enabled successfully
|
|
|
|
|
}
|
|
|
|
|
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, you'll need to verify their 2FA code. If they have 2FA enabled, they'll be redirected to the `twoFactorPage` where they can enter their 2FA code.
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
```ts title="sign-in.ts"
|
|
|
|
|
const signin = async () => {
|
|
|
|
|
const { data, error } = await client.signIn.email({
|
|
|
|
|
await client.signIn.email({
|
|
|
|
|
email: "user@example.com",
|
|
|
|
|
password: "password123",
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
By default, the user will be redirected to the `twoFactorPage` if they have 2FA enabled. If you want to handle the 2FA verification in place, you can use the `options` parameter.
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
```ts title="sing-in.ts"
|
|
|
|
|
import { client } from "better-auth/client";
|
|
|
|
|
import { twoFactorClient } from "better-auth/client/plugins";
|
|
|
|
|
|
|
|
|
|
const client = createAuthClient({
|
|
|
|
|
plugins: [twoFactorClient({
|
|
|
|
|
redirect: false
|
|
|
|
|
})]
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const signin = async () => {
|
|
|
|
|
const { data, error } = await client.signIn.email({
|
|
|
|
|
await client.signIn.email({
|
|
|
|
|
email: "user@example.com",
|
|
|
|
|
password: "password123",
|
|
|
|
|
fetchOptions: {
|
|
|
|
|
}, {
|
|
|
|
|
async onSuccess(context) {
|
|
|
|
|
if (context.data.twoFactorRedirect) {
|
|
|
|
|
// Handle the 2FA verification in place
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
@@ -126,7 +132,7 @@ const signin = async () => {
|
|
|
|
|
|
|
|
|
|
### TOTP
|
|
|
|
|
|
|
|
|
|
TOTP is a time-based one-time password algorithm that generates a code based on the current time. It's a more secure method than OTP because it takes into account the time it takes to generate the code.
|
|
|
|
|
TOTP is a time-based one-time password algorithm that generates a code based on the current time.
|
|
|
|
|
|
|
|
|
|
#### Getting TOTP URI
|
|
|
|
|
|
|
|
|
|
@@ -139,19 +145,30 @@ if (data) {
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### Generating a QR Code
|
|
|
|
|
**Example: Using React**
|
|
|
|
|
|
|
|
|
|
You can use a library like `qrcode` to generate a QR code from the TOTP URI.
|
|
|
|
|
```tsx title="user-card.tsx"
|
|
|
|
|
import QRCode from "react-qr-code";
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
import { toCanvas } from "qrcode"
|
|
|
|
|
|
|
|
|
|
toCanvas(document.getElementById('canvas'), data.totpURI)
|
|
|
|
|
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
|
|
|
|
|
After the user has entered their 2FA code, you can verify it usinng `twoFacotr.verifyTotp` method.
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const verifyTotp = async (code: string) => {
|
|
|
|
|
@@ -161,8 +178,9 @@ const verifyTotp = async (code: string) => {
|
|
|
|
|
|
|
|
|
|
### OTP
|
|
|
|
|
|
|
|
|
|
OTP is a one-time password sent to the user's email or phone.
|
|
|
|
|
Before using OTP, you need to setup `sendOTP` function.
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
```ts title="auth.ts" twoslash
|
|
|
|
|
import { betterAuth } from "better-auth"
|
|
|
|
|
@@ -187,12 +205,12 @@ export const auth = await betterAuth({
|
|
|
|
|
|
|
|
|
|
#### Sending OTP
|
|
|
|
|
|
|
|
|
|
sending otp is done by calling `sendOtp` function. This functino will call your `sendOTP` function that you provide in the `otpOptions` with the otp code and the user.
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const { data, error } = await client.twoFactor.sendOtp()
|
|
|
|
|
if (data) {
|
|
|
|
|
// Show the OTP to the user
|
|
|
|
|
// redirect or show the user to enter the code
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
@@ -202,14 +220,20 @@ After the user has entered their OTP code, you can verify it
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const verifyOtp = async (code: string) => {
|
|
|
|
|
const { data, error } = await client.twoFactor.verifyOtp({ code })
|
|
|
|
|
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 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.
|
|
|
|
|
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:
|
|
|
|
|
@@ -223,13 +247,17 @@ if (data) {
|
|
|
|
|
|
|
|
|
|
#### Using Backup Codes
|
|
|
|
|
|
|
|
|
|
Backup codes can be used to recover access to the account if the user loses access to their phone or email.
|
|
|
|
|
You can now allow users to provider backup code as account recover method.
|
|
|
|
|
|
|
|
|
|
```ts
|
|
|
|
|
const { data, error } = await client.twoFactor.verifyBackupCode({code: ""})
|
|
|
|
|
if (data) {
|
|
|
|
|
// 2FA verified and account recovered
|
|
|
|
|
await client.twoFactor.verifyBackupCode({code: ""}, {
|
|
|
|
|
onSuccess(){
|
|
|
|
|
//redirect the user on success
|
|
|
|
|
},
|
|
|
|
|
onError(ctx){
|
|
|
|
|
alert(ctx.error.message)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Trusted Devices
|
|
|
|
|
@@ -251,9 +279,14 @@ const verify2FA = async (code: string) => {
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
<Callout type="info">
|
|
|
|
|
Trusted devices enhance user convenience but should be used carefully, as they slightly reduce security. Encourage users to only trust personal, secure devices.
|
|
|
|
|
</Callout>
|
|
|
|
|
|
|
|
|
|
## 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
|
|
|
|
|
|
|
|
|
|
@@ -338,6 +371,7 @@ const client = createAuthClient({
|
|
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
**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.
|
|
|
|
|
|