- Add Superforms + Zod 4 for all admin form validation - Replace address isDefault with three separate boolean defaults (Mailing, Billing, Shipping) - Update profiles page with three-checkbox address form and toast improvements - Convert all admin page server actions to pass request directly to crud - Export superFormErrorsToFieldErrors helper for profiles server actions - Regenerate Prisma client for new schema fields
212 lines
5.7 KiB
Markdown
212 lines
5.7 KiB
Markdown
---
|
|
name: email-and-password-best-practices
|
|
description: Configure email verification, implement password reset flows, set password policies, and customise hashing algorithms for Better Auth email/password authentication. Use when users need to set up login, sign-in, sign-up, credential authentication, or password security with Better Auth.
|
|
---
|
|
|
|
## Quick Start
|
|
|
|
1. Enable email/password: `emailAndPassword: { enabled: true }`
|
|
2. Configure `emailVerification.sendVerificationEmail`
|
|
3. Add `sendResetPassword` for password reset flows
|
|
4. Run `npx @better-auth/cli@latest migrate`
|
|
5. Verify: attempt sign-up and confirm verification email triggers
|
|
|
|
---
|
|
|
|
## Email Verification Setup
|
|
|
|
Configure `emailVerification.sendVerificationEmail` to verify user email addresses.
|
|
|
|
```ts
|
|
import { betterAuth } from 'better-auth';
|
|
import { sendEmail } from './email'; // your email sending function
|
|
|
|
export const auth = betterAuth({
|
|
emailVerification: {
|
|
sendVerificationEmail: async ({ user, url, token }, request) => {
|
|
await sendEmail({
|
|
to: user.email,
|
|
subject: 'Verify your email address',
|
|
text: `Click the link to verify your email: ${url}`
|
|
});
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
**Note**: The `url` parameter contains the full verification link. The `token` is available if you need to build a custom verification URL.
|
|
|
|
### Requiring Email Verification
|
|
|
|
For stricter security, enable `emailAndPassword.requireEmailVerification` to block sign-in until the user verifies their email. When enabled, unverified users will receive a new verification email on each sign-in attempt.
|
|
|
|
```ts
|
|
export const auth = betterAuth({
|
|
emailAndPassword: {
|
|
requireEmailVerification: true
|
|
}
|
|
});
|
|
```
|
|
|
|
**Note**: This requires `sendVerificationEmail` to be configured and only applies to email/password sign-ins.
|
|
|
|
## Client Side Validation
|
|
|
|
Implement client-side validation for immediate user feedback and reduced server load.
|
|
|
|
## Callback URLs
|
|
|
|
Always use absolute URLs (including the origin) for callback URLs in sign-up and sign-in requests. This prevents Better Auth from needing to infer the origin, which can cause issues when your backend and frontend are on different domains.
|
|
|
|
```ts
|
|
const { data, error } = await authClient.signUp.email({
|
|
callbackURL: 'https://example.com/callback' // absolute URL with origin
|
|
});
|
|
```
|
|
|
|
## Password Reset Flows
|
|
|
|
Provide `sendResetPassword` in the email and password config to enable password resets.
|
|
|
|
```ts
|
|
import { betterAuth } from 'better-auth';
|
|
import { sendEmail } from './email'; // your email sending function
|
|
|
|
export const auth = betterAuth({
|
|
emailAndPassword: {
|
|
enabled: true,
|
|
// Custom email sending function to send reset-password email
|
|
sendResetPassword: async ({ user, url, token }, request) => {
|
|
void sendEmail({
|
|
to: user.email,
|
|
subject: 'Reset your password',
|
|
text: `Click the link to reset your password: ${url}`
|
|
});
|
|
},
|
|
// Optional event hook
|
|
onPasswordReset: async ({ user }, request) => {
|
|
// your logic here
|
|
console.log(`Password for user ${user.email} has been reset.`);
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
### Security Considerations
|
|
|
|
Built-in protections: background email sending (timing attack prevention), dummy operations on invalid requests, constant response messages regardless of user existence.
|
|
|
|
On serverless platforms, configure a background task handler:
|
|
|
|
```ts
|
|
export const auth = betterAuth({
|
|
advanced: {
|
|
backgroundTasks: {
|
|
handler: (promise) => {
|
|
// Use platform-specific methods like waitUntil
|
|
waitUntil(promise);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
#### Token Security
|
|
|
|
Tokens expire after 1 hour by default. Configure with `resetPasswordTokenExpiresIn` (in seconds):
|
|
|
|
```ts
|
|
export const auth = betterAuth({
|
|
emailAndPassword: {
|
|
enabled: true,
|
|
resetPasswordTokenExpiresIn: 60 * 30 // 30 minutes
|
|
}
|
|
});
|
|
```
|
|
|
|
Tokens are single-use — deleted immediately after successful reset.
|
|
|
|
#### Session Revocation
|
|
|
|
Enable `revokeSessionsOnPasswordReset` to invalidate all existing sessions on password reset:
|
|
|
|
```ts
|
|
export const auth = betterAuth({
|
|
emailAndPassword: {
|
|
enabled: true,
|
|
revokeSessionsOnPasswordReset: true
|
|
}
|
|
});
|
|
```
|
|
|
|
#### Password Requirements
|
|
|
|
Password length limits (configurable):
|
|
|
|
```ts
|
|
export const auth = betterAuth({
|
|
emailAndPassword: {
|
|
enabled: true,
|
|
minPasswordLength: 12,
|
|
maxPasswordLength: 256
|
|
}
|
|
});
|
|
```
|
|
|
|
### Sending the Password Reset
|
|
|
|
Call `requestPasswordReset` to send the reset link. Triggers the `sendResetPassword` function from your config.
|
|
|
|
```ts
|
|
const data = await auth.api.requestPasswordReset({
|
|
body: {
|
|
email: 'john.doe@example.com', // required
|
|
redirectTo: 'https://example.com/reset-password'
|
|
}
|
|
});
|
|
```
|
|
|
|
Or authClient:
|
|
|
|
```ts
|
|
const { data, error } = await authClient.requestPasswordReset({
|
|
email: 'john.doe@example.com', // required
|
|
redirectTo: 'https://example.com/reset-password'
|
|
});
|
|
```
|
|
|
|
**Note**: While the `email` is required, we also recommend configuring the `redirectTo` for a smoother user experience.
|
|
|
|
## Password Hashing
|
|
|
|
Default: `scrypt` (Node.js native, no external dependencies).
|
|
|
|
### Custom Hashing Algorithm
|
|
|
|
To use Argon2id or another algorithm, provide custom `hash` and `verify` functions:
|
|
|
|
```ts
|
|
import { betterAuth } from 'better-auth';
|
|
import { hash, verify, type Options } from '@node-rs/argon2';
|
|
|
|
const argon2Options: Options = {
|
|
memoryCost: 65536, // 64 MiB
|
|
timeCost: 3, // 3 iterations
|
|
parallelism: 4, // 4 parallel lanes
|
|
outputLen: 32, // 32 byte output
|
|
algorithm: 2 // Argon2id variant
|
|
};
|
|
|
|
export const auth = betterAuth({
|
|
emailAndPassword: {
|
|
enabled: true,
|
|
password: {
|
|
hash: (password) => hash(password, argon2Options),
|
|
verify: ({ password, hash: storedHash }) => verify(storedHash, password, argon2Options)
|
|
}
|
|
}
|
|
});
|
|
```
|
|
|
|
**Note**: If you switch hashing algorithms on an existing system, users with passwords hashed using the old algorithm won't be able to sign in. Plan a migration strategy if needed.
|