Requirements
- Target platform
- OpenClaw
- Install method
- Manual import
- Extraction
- Extract archive
- Prerequisites
- OpenClaw
- Primary doc
- SKILL.md
This skill provides guidance and enforcement rules for implementing secure two-factor authentication (2FA) using Better Auth's twoFactor plugin.
This skill provides guidance and enforcement rules for implementing secure two-factor authentication (2FA) using Better Auth's twoFactor plugin.
Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.
I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete.
I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run.
When adding 2FA to your application, configure the twoFactor plugin with your app name as the issuer. This name appears in authenticator apps when users scan the QR code. import { betterAuth } from "better-auth"; import { twoFactor } from "better-auth/plugins"; export const auth = betterAuth({ appName: "My App", // Used as the default issuer for TOTP plugins: [ twoFactor({ issuer: "My App", // Optional: override the app name for 2FA specifically }), ], }); Note: After adding the plugin, run npx @better-auth/cli migrate to add the required database fields and tables.
Add the client plugin and configure the redirect behavior for 2FA verification: import { createAuthClient } from "better-auth/client"; import { twoFactorClient } from "better-auth/client/plugins"; export const authClient = createAuthClient({ plugins: [ twoFactorClient({ onTwoFactorRedirect() { window.location.href = "/2fa"; // Redirect to your 2FA verification page }, }), ], });
When a user enables 2FA, require their password for verification. The enable endpoint returns a TOTP URI for QR code generation and backup codes for account recovery. const enable2FA = async (password: string) => { const { data, error } = await authClient.twoFactor.enable({ password, }); if (data) { // data.totpURI - Use this to generate a QR code // data.backupCodes - Display these to the user for safekeeping } }; Important: The twoFactorEnabled flag on the user is not set to true until the user successfully verifies their first TOTP code. This ensures users have properly configured their authenticator app before 2FA is fully active.
If you want to enable 2FA immediately without requiring verification, set skipVerificationOnEnable: twoFactor({ skipVerificationOnEnable: true, // Not recommended for most use cases }); Note: This is generally not recommended as it doesn't confirm the user has successfully set up their authenticator app.
TOTP generates time-based codes using an authenticator app (Google Authenticator, Authy, etc.). Codes are valid for 30 seconds by default.
Use the TOTP URI to generate a QR code for users to scan: import QRCode from "react-qr-code"; const TotpSetup = ({ totpURI }: { totpURI: string }) => { return <QRCode value={totpURI} />; };
Better Auth accepts codes from one period before and one after the current time, accommodating minor clock differences between devices: const verifyTotp = async (code: string) => { const { data, error } = await authClient.twoFactor.verifyTotp({ code, trustDevice: true, // Optional: remember this device for 30 days }); };
twoFactor({ totpOptions: { digits: 6, // 6 or 8 digits (default: 6) period: 30, // Code validity period in seconds (default: 30) }, });
OTP sends a one-time code to the user's email or phone. You must implement the sendOTP function to deliver codes.
import { betterAuth } from "better-auth"; import { twoFactor } from "better-auth/plugins"; import { sendEmail } from "./email"; export const auth = betterAuth({ plugins: [ twoFactor({ otpOptions: { sendOTP: async ({ user, otp }, ctx) => { await sendEmail({ to: user.email, subject: "Your verification code", text: `Your code is: ${otp}`, }); }, period: 5, // Code validity in minutes (default: 3) digits: 6, // Number of digits (default: 6) allowedAttempts: 5, // Max verification attempts (default: 5) }, }), ], });
// Request an OTP to be sent const sendOtp = async () => { const { data, error } = await authClient.twoFactor.sendOtp(); }; // Verify the OTP code const verifyOtp = async (code: string) => { const { data, error } = await authClient.twoFactor.verifyOtp({ code, trustDevice: true, }); };
Configure how OTP codes are stored in the database: twoFactor({ otpOptions: { storeOTP: "encrypted", // Options: "plain", "encrypted", "hashed" }, }); For custom encryption: twoFactor({ otpOptions: { storeOTP: { encrypt: async (token) => myEncrypt(token), decrypt: async (token) => myDecrypt(token), }, }, });
Backup codes provide account recovery when users lose access to their authenticator app or phone. They are generated automatically when 2FA is enabled.
Always show backup codes to users when they enable 2FA: const BackupCodes = ({ codes }: { codes: string[] }) => { return ( <div> <p>Save these codes in a secure location:</p> <ul> {codes.map((code, i) => ( <li key={i}>{code}</li> ))} </ul> </div> ); };
When users need new codes, regenerate them (this invalidates all previous codes): const regenerateBackupCodes = async (password: string) => { const { data, error } = await authClient.twoFactor.generateBackupCodes({ password, }); // data.backupCodes contains the new codes };
const verifyBackupCode = async (code: string) => { const { data, error } = await authClient.twoFactor.verifyBackupCode({ code, trustDevice: true, }); }; Note: Each backup code can only be used once and is removed from the database after successful verification.
twoFactor({ backupCodeOptions: { amount: 10, // Number of codes to generate (default: 10) length: 10, // Length of each code (default: 10) storeBackupCodes: "encrypted", // Options: "plain", "encrypted" }, });
When a user with 2FA enabled signs in, the response includes twoFactorRedirect: true: const signIn = async (email: string, password: string) => { const { data, error } = await authClient.signIn.email( { email, password, }, { onSuccess(context) { if (context.data.twoFactorRedirect) { // Redirect to 2FA verification page window.location.href = "/2fa"; } }, } ); };
When using auth.api.signInEmail on the server, check for 2FA redirect: const response = await auth.api.signInEmail({ body: { email: "user@example.com", password: "password", }, }); if ("twoFactorRedirect" in response) { // Handle 2FA verification }
Trusted devices allow users to skip 2FA verification on subsequent sign-ins for a configurable period.
Pass trustDevice: true when verifying 2FA: await authClient.twoFactor.verifyTotp({ code: "123456", trustDevice: true, });
twoFactor({ trustDeviceMaxAge: 30 * 24 * 60 * 60, // 30 days in seconds (default) }); Note: The trust period refreshes on each successful sign-in within the trust window.
During the 2FA flow: User signs in with credentials Session cookie is removed (not yet authenticated) A temporary two-factor cookie is set (default: 10-minute expiration) User verifies via TOTP, OTP, or backup code Session cookie is created upon successful verification Configure the two-factor cookie expiration: twoFactor({ twoFactorCookieMaxAge: 600, // 10 minutes in seconds (default) });
Better Auth applies built-in rate limiting to all 2FA endpoints (3 requests per 10 seconds). For OTP verification, additional attempt limiting is applied: twoFactor({ otpOptions: { allowedAttempts: 5, // Max attempts per OTP code (default: 5) }, });
TOTP secrets are encrypted using symmetric encryption with your auth secret Backup codes are stored encrypted by default OTP codes can be configured for plain, encrypted, or hashed storage
Better Auth uses constant-time comparison for OTP verification to prevent timing attacks.
Two-factor authentication can only be enabled for credential (email/password) accounts. For social accounts, it's assumed the provider already handles 2FA.
Allow users to disable 2FA with password confirmation: const disable2FA = async (password: string) => { const { data, error } = await authClient.twoFactor.disable({ password, }); }; Note: When 2FA is disabled, trusted device records are revoked.
import { betterAuth } from "better-auth"; import { twoFactor } from "better-auth/plugins"; import { sendEmail } from "./email"; export const auth = betterAuth({ appName: "My App", plugins: [ twoFactor({ // TOTP settings issuer: "My App", totpOptions: { digits: 6, period: 30, }, // OTP settings otpOptions: { sendOTP: async ({ user, otp }) => { await sendEmail({ to: user.email, subject: "Your verification code", text: `Your code is: ${otp}`, }); }, period: 5, allowedAttempts: 5, storeOTP: "encrypted", }, // Backup code settings backupCodeOptions: { amount: 10, length: 10, storeBackupCodes: "encrypted", }, // Session settings twoFactorCookieMaxAge: 600, // 10 minutes trustDeviceMaxAge: 30 * 24 * 60 * 60, // 30 days }), ], });
Identity, auth, scanning, governance, audit, and operational guardrails.
Largest current source with strong distribution and engagement signals.