# One-Time Token Plugin

> Secure verification tokens for sensitive operations

*Canonical: https://makerkit.dev/docs/nextjs-prisma/better-auth/one-time-token-plugin*

---

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](./overview).

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

| Setting | Value | Description |
|---------|-------|-------------|
| Expiration | 10 minutes | Token validity period |
| Storage | Hashed | Tokens stored securely in database |
| Length | 6 digits | Code format (100000-999999) |

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

## Usage Example

```typescript
// 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.

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

---

**Previous:** [Rate Limiting](./rate-limiting)
