One-Time Token Plugin

Secure verification tokens for sensitive operations

Require email verification before destructive actions like account deletion - users receive a 6-digit code that expires in 10 minutes and can only be used once.

This page is part of the Authentication documentation.

The one-time token plugin generates cryptographically secure verification codes for sensitive operations. When a user attempts a destructive action (account deletion, organization deletion), they receive an email with a 6-digit code. The code must be entered to confirm the action. Tokens are stored as hashed values, expire after 10 minutes, and can only be used once - providing defense in depth against accidental or unauthorized destructive actions.

A one-time token is a cryptographically secure, single-use verification code sent via email to confirm sensitive operations before they execute.

  • Use one-time tokens when: you need email confirmation for destructive or irreversible actions (deleting accounts, removing data, changing email addresses).
  • Use regular confirmation dialogs when: the action is reversible or doesn't require the security of email verification.

Use Cases

  • Account deletion verification
  • Organization deletion verification
  • Email address changes
  • Sensitive profile changes
  • Any irreversible operation

How It Works

  1. User initiates sensitive action (e.g., delete account)
  2. System generates secure 6-digit code
  3. Code is sent to user's verified email
  4. User enters code in confirmation dialog
  5. Code is verified and action executes
  6. Token is invalidated (single use)

Configuration

SettingValueDescription
Expiration10 minutesToken validity period
StorageHashedTokens stored securely in database
Length6 digitsCode format (100000-999999)

The plugin is enabled by default with no additional configuration needed.

Usage Example

// 1. Request verification code (sent via email)
await authClient.oneTimeToken.sendToken({
type: 'account-delete',
});
// 2. User receives email with 6-digit code
// 3. Verify code and complete action
await authClient.oneTimeToken.verifyToken({
token: '847293',
type: 'account-delete',
});

Development Mode

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

[DEV] Verification code: 847293

This allows testing without checking email.

Security Features

  • Cryptographic randomness: Uses crypto.getRandomValues() for unpredictable codes
  • Hashed storage: Tokens stored as hashes - database compromise doesn't expose codes
  • Single use: Tokens invalidate immediately after verification
  • Short expiration: 10-minute window limits attack surface

Common Pitfalls

  • Skipping verification for "convenience": Destructive actions should always require verification. Don't bypass this for admin users.
  • Not rate-limiting token requests: Users can spam token emails. The plugin has built-in rate limiting - don't disable it.
  • Long expiration windows: 10 minutes is generous. Longer windows increase risk of intercepted codes.
  • Showing token type in error messages: Don't reveal what action the token was for in error messages - it aids social engineering.
  • Forgetting development console output: In dev mode, check the terminal instead of email.

Frequently Asked Questions

Why 6 digits instead of longer codes?
6 digits (1 million combinations) with short expiration and rate limiting provides adequate security while being easy to type. Longer codes frustrate users.
Can I customize the expiration time?
Yes. Modify the expiresIn option in the plugin configuration at packages/better-auth/src/plugins/one-time-token.ts.
What happens if the user requests multiple codes?
Previous codes are invalidated. Only the most recent code works.
Is this different from the OTP plugin?
Yes. OTP is for authentication flows (sign-in, email verification). One-time tokens are for confirming sensitive actions after the user is already authenticated.
Can I add one-time token verification to custom actions?
Yes. Use the plugin API to generate and verify tokens for any custom action type.

Previous: Rate Limiting