OTP Plugin

Email-based one-time password authentication

The OTP plugin provides email-based one-time password authentication for passwordless sign-in, email verification, and password reset flows.

Overview

The OTP (One-Time Password) plugin enables authentication via 6-digit codes sent to the user's email. This provides:

  • Passwordless sign-in option
  • Email verification without clicking links
  • Secure password reset flow

Configuration

The plugin is enabled by default and requires no additional environment variables. It uses the configured mailer to send OTP codes.

Email Configuration

The OTP plugin uses your existing email configuration:

MAILER_PROVIDER=nodemailer
EMAIL_SENDER=noreply@yourapp.com
EMAIL_HOST=smtp.example.com
EMAIL_PORT=587
EMAIL_USER=your_user
EMAIL_PASSWORD=your_password

How It Works

OTP Types

The plugin supports three OTP types:

TypePurposeEmail Template
sign-inPasswordless authenticationotp-sign-in.email.tsx
email-verificationVerify email addressotp-email-verification.email.tsx
forget-passwordPassword resetotp-password-reset.email.tsx

OTP Lifecycle

  1. User requests an OTP (via sign-in, verification, or password reset)
  2. A 6-digit code is generated and sent via email
  3. User enters the code in the application
  4. Code is validated and action is completed

Development Mode

In development (NODE_ENV=development), OTP codes are logged to the console for testing:

[DEV] OTP for user@example.com (sign-in): 847293

This allows testing without checking email in development.

Implementation Details

Location

packages/better-auth/src/plugins/otp-auth.ts

Email Templates

OTP emails use dedicated templates in packages/email-templates/src/emails/:

  • otp-sign-in.email.tsx - Sign-in OTP
  • otp-email-verification.email.tsx - Email verification OTP
  • otp-password-reset.email.tsx - Password reset OTP
  • otp.email.tsx - Generic OTP template with i18n support

Integration

The plugin integrates with Better Auth's emailOTP plugin:

import { emailOTP } from 'better-auth/plugins/email-otp';
const otpPlugin = emailOTP({
sendVerificationOTP: async ({ email, otp, type }) => {
// Routes to appropriate email template based on type
switch (type) {
case 'sign-in':
await sendOtpSignInEmail({ email, otp });
break;
case 'email-verification':
await sendOtpEmailVerificationEmail({ email, otp });
break;
case 'forget-password':
await sendOtpPasswordResetEmail({ email, otp });
break;
}
},
});

Usage Example

Client-Side Sign-In with OTP

import { authClient } from '@kit/better-auth/client';
// Request OTP
await authClient.emailOtp.sendVerificationOtp({
email: 'user@example.com',
type: 'sign-in',
});
// Verify OTP
const result = await authClient.emailOtp.verifyOtp({
email: 'user@example.com',
otp: '123456',
});

Next: Rate Limiting