two-factor-authentication-best-practices
This skill provides guidance and enforcement rules for implementing secure two-factor authentication (2FA) using Better Auth's twoFactor plugin.
Best use case
two-factor-authentication-best-practices is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
This skill provides guidance and enforcement rules for implementing secure two-factor authentication (2FA) using Better Auth's twoFactor plugin.
Teams using two-factor-authentication-best-practices should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/two-factor-authentication-best-practices/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How two-factor-authentication-best-practices Compares
| Feature / Agent | two-factor-authentication-best-practices | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
This skill provides guidance and enforcement rules for implementing secure two-factor authentication (2FA) using Better Auth's twoFactor plugin.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
## Setting Up Two-Factor Authentication
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.
```ts
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.
### Client-Side Setup
Add the client plugin and configure the redirect behavior for 2FA verification:
```ts
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
},
}),
],
});
```
## Enabling 2FA for Users
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.
```ts
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.
### Skipping Initial Verification
If you want to enable 2FA immediately without requiring verification, set `skipVerificationOnEnable`:
```ts
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 (Authenticator App)
TOTP generates time-based codes using an authenticator app (Google Authenticator, Authy, etc.). Codes are valid for 30 seconds by default.
### Displaying the QR Code
Use the TOTP URI to generate a QR code for users to scan:
```tsx
import QRCode from "react-qr-code";
const TotpSetup = ({ totpURI }: { totpURI: string }) => {
return <QRCode value={totpURI} />;
};
```
### Verifying TOTP Codes
Better Auth accepts codes from one period before and one after the current time, accommodating minor clock differences between devices:
```ts
const verifyTotp = async (code: string) => {
const { data, error } = await authClient.twoFactor.verifyTotp({
code,
trustDevice: true, // Optional: remember this device for 30 days
});
};
```
### TOTP Configuration Options
```ts
twoFactor({
totpOptions: {
digits: 6, // 6 or 8 digits (default: 6)
period: 30, // Code validity period in seconds (default: 30)
},
});
```
## OTP (Email/SMS)
OTP sends a one-time code to the user's email or phone. You must implement the `sendOTP` function to deliver codes.
### Configuring OTP Delivery
```ts
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)
},
}),
],
});
```
### Sending and Verifying OTP
```ts
// 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,
});
};
```
### OTP Storage Security
Configure how OTP codes are stored in the database:
```ts
twoFactor({
otpOptions: {
storeOTP: "encrypted", // Options: "plain", "encrypted", "hashed"
},
});
```
For custom encryption:
```ts
twoFactor({
otpOptions: {
storeOTP: {
encrypt: async (token) => myEncrypt(token),
decrypt: async (token) => myDecrypt(token),
},
},
});
```
## Backup Codes
Backup codes provide account recovery when users lose access to their authenticator app or phone. They are generated automatically when 2FA is enabled.
### Displaying Backup Codes
Always show backup codes to users when they enable 2FA:
```tsx
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>
);
};
```
### Regenerating Backup Codes
When users need new codes, regenerate them (this invalidates all previous codes):
```ts
const regenerateBackupCodes = async (password: string) => {
const { data, error } = await authClient.twoFactor.generateBackupCodes({
password,
});
// data.backupCodes contains the new codes
};
```
### Using Backup Codes for Recovery
```ts
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.
### Backup Code Configuration
```ts
twoFactor({
backupCodeOptions: {
amount: 10, // Number of codes to generate (default: 10)
length: 10, // Length of each code (default: 10)
storeBackupCodes: "encrypted", // Options: "plain", "encrypted"
},
});
```
## Handling 2FA During Sign-In
When a user with 2FA enabled signs in, the response includes `twoFactorRedirect: true`:
```ts
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";
}
},
}
);
};
```
### Server-Side 2FA Detection
When using `auth.api.signInEmail` on the server, check for 2FA redirect:
```ts
const response = await auth.api.signInEmail({
body: {
email: "user@example.com",
password: "password",
},
});
if ("twoFactorRedirect" in response) {
// Handle 2FA verification
}
```
## Trusted Devices
Trusted devices allow users to skip 2FA verification on subsequent sign-ins for a configurable period.
### Enabling Trust on Verification
Pass `trustDevice: true` when verifying 2FA:
```ts
await authClient.twoFactor.verifyTotp({
code: "123456",
trustDevice: true,
});
```
### Configuring Trust Duration
```ts
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.
## Security Considerations
### Session Management
During the 2FA flow:
1. User signs in with credentials
2. Session cookie is removed (not yet authenticated)
3. A temporary two-factor cookie is set (default: 10-minute expiration)
4. User verifies via TOTP, OTP, or backup code
5. Session cookie is created upon successful verification
Configure the two-factor cookie expiration:
```ts
twoFactor({
twoFactorCookieMaxAge: 600, // 10 minutes in seconds (default)
});
```
### Rate Limiting
Better Auth applies built-in rate limiting to all 2FA endpoints (3 requests per 10 seconds). For OTP verification, additional attempt limiting is applied:
```ts
twoFactor({
otpOptions: {
allowedAttempts: 5, // Max attempts per OTP code (default: 5)
},
});
```
### Encryption at Rest
- 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
### Constant-Time Comparison
Better Auth uses constant-time comparison for OTP verification to prevent timing attacks.
### Credential Account Requirement
Two-factor authentication can only be enabled for credential (email/password) accounts. For social accounts, it's assumed the provider already handles 2FA.
## Disabling 2FA
Allow users to disable 2FA with password confirmation:
```ts
const disable2FA = async (password: string) => {
const { data, error } = await authClient.twoFactor.disable({
password,
});
};
```
**Note**: When 2FA is disabled, trusted device records are revoked.
## Complete Configuration Example
```ts
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
}),
],
});
```Related Skills
fix-bad-practices
Fix bad coding practices identified by audit, following fail-fast principles
cursor-best-practices
Best practices for using Cursor—rules, commands, skills, subagents, ignore files, Agent security, workflows, and community resources. Use when setting up Cursor, initializing or creating the .cursor folder, writing .cursor/rules or AGENTS.md, creating commands or skills, configuring .cursorignore, working with Agent, discovering rules or MCPs (e.g. cursor.directory), making codebases cursor-compatible, or asking about Cursor workflows, TDD, git commands, or large codebases.
ai-factory
Set up Claude Code context for a project. Analyzes tech stack, installs relevant skills from skills.sh, generates custom skills, and configures MCP servers. Use when starting new project, setting up AI context, or asking "set up project", "configure AI", "what skills do I need".
ai-factory.fix
Fix a specific bug or problem in the codebase. Analyzes code to find and fix issues without creating plans. Use when user reports a bug, error, or something not working. Always suggests test coverage and adds logging.
ai-ad-code-factory
No description provided.
agent-md-refactor
Refactor bloated AGENTS.md, CLAUDE.md, or similar agent instruction files to follow progressive disclosure principles. Splits monolithic files into organized, linked documentation. Use when (1) agent context files are too large or unwieldy, (2) need to separate project context from executable skills, (3) want to create modular documentation structure, (4) refactoring existing documentation for better organization, or (5) creating new agent context documentation from scratch.
agent-factory
Claude Code agent generation system that creates custom agents and sub-agents with enhanced YAML frontmatter, tool access patterns, and MCP integration support following proven production patterns
advanced-math-trading/portfolio-factors
Factor modeling and portfolio construction (Markowitz, Black-Litterman, constraints, turnover).
acc-create-factory
Generates DDD Factory for PHP 8.5. Creates factories for complex domain object instantiation with validation and encapsulated creation logic. Includes unit tests.
remotion-best-practices
Best practices for Remotion - Video creation in React
power-bi-security-rls-best-practices
Comprehensive Power BI Row-Level Security (RLS) and advanced security patterns implementation guide with dynamic security, best practices, and governance strategies. Triggers on: **/*.{pbix,dax,md,txt,json,csharp,powershell}
amazon-bestseller-launch
Complete Amazon KDP bestseller launch system with proven strategies for achieving