| 1 | --- |
| 2 | name: email-and-password-best-practices |
| 3 | 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. |
| 4 | --- |
| 5 | |
| 6 | ## Quick Start |
| 7 | |
| 8 | 1. Enable email/password: `emailAndPassword: { enabled: true }` |
| 9 | 2. Configure `emailVerification.sendVerificationEmail` |
| 10 | 3. Add `sendResetPassword` for password reset flows |
| 11 | 4. Run `npx @better-auth/cli@latest migrate` |
| 12 | 5. Verify: attempt sign-up and confirm verification email triggers |
| 13 | |
| 14 | --- |
| 15 | |
| 16 | ## Email Verification Setup |
| 17 | |
| 18 | Configure `emailVerification.sendVerificationEmail` to verify user email addresses. |
| 19 | |
| 20 | ```ts |
| 21 | import { betterAuth } |
| 22 | import { sendEmail } |
| 23 | |
| 24 | export const auth = |
| 25 | emailVerification: { |
| 26 | sendVerificationEmail: async ( |
| 27 | await sendEmail({ |
| 28 | to: user.email |
| 29 | subject: "Verify your email address", |
| 30 | text: `Click the link to verify your email: ${url |
| 31 | }); |
| 32 | }, |
| 33 | }, |
| 34 | }); |
| 35 | ``` |
| 36 | |
| 37 | **Note**: The `url |
| 38 | |
| 39 | ### Requiring Email Verification |
| 40 | |
| 41 | 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. |
| 42 | |
| 43 | ```ts |
| 44 | export const auth = |
| 45 | emailAndPassword: { |
| 46 | requireEmailVerification: true, |
| 47 | }, |
| 48 | }); |
| 49 | ``` |
| 50 | |
| 51 | **Note**: This requires `sendVerificationEmail |
| 52 | |
| 53 | ## Client-Side Validation |
| 54 | |
| 55 | Implement client-side validation for immediate user feedback and reduced server load. |
| 56 | |
| 57 | ## Callback URLs |
| 58 | |
| 59 | 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. |
| 60 | |
| 61 | ```ts |
| 62 | const { data, |
| 63 | callbackURL: "https://example.com/callback", // |
| 64 | }); |
| 65 | ``` |
| 66 | |
| 67 | ## Password Reset Flows |
| 68 | |
| 69 | Provide `sendResetPassword` in the email and password config to enable password resets. |
| 70 | |
| 71 | ```ts |
| 72 | import { betterAuth } |
| 73 | import { sendEmail } |
| 74 | |
| 75 | export const auth = |
| 76 | emailAndPassword: { |
| 77 | enabled: true, |
| 78 | // Custom email sending function to send reset-password email |
| 79 | sendResetPassword: async ( |
| 80 | void sendEmail({ |
| 81 | to: user.email |
| 82 | subject: "Reset your password", |
| 83 | text: `Click the link to reset your password: ${url |
| 84 | }); |
| 85 | }, |
| 86 | // Optional event hook |
| 87 | onPasswordReset: async ( |
| 88 | // your logic here |
| 89 | console.log(`Password for user |
| 90 | }, |
| 91 | }, |
| 92 | }); |
| 93 | ``` |
| 94 | |
| 95 | ### Security Considerations |
| 96 | |
| 97 | Built-in protections: background email sending (timing attack prevention), dummy operations on invalid requests, constant response messages regardless of user existence. |
| 98 | |
| 99 | On serverless platforms, configure a background task handler: |
| 100 | |
| 101 | ```ts |
| 102 | export const auth = |
| 103 | advanced: { |
| 104 | backgroundTasks: { |
| 105 | handler: (promise) |
| 106 | // Use platform-specific methods like waitUntil |
| 107 | waitUntil(promise); |
| 108 | }, |
| 109 | }, |
| 110 | }, |
| 111 | }); |
| 112 | ``` |
| 113 | |
| 114 | #### Token Security |
| 115 | |
| 116 | Tokens expire after 1 hour by default. Configure with `resetPasswordTokenExpiresIn` (in seconds): |
| 117 | |
| 118 | ```ts |
| 119 | export const auth = |
| 120 | emailAndPassword: { |
| 121 | enabled: true, |
| 122 | resetPasswordTokenExpiresIn: 60 * 30, |
| 123 | }, |
| 124 | }); |
| 125 | ``` |
| 126 | |
| 127 | Tokens are single-use — deleted immediately after successful reset. |
| 128 | |
| 129 | #### Session Revocation |
| 130 | |
| 131 | Enable `revokeSessionsOnPasswordReset` to invalidate all existing sessions on password reset: |
| 132 | |
| 133 | ```ts |
| 134 | export const auth = |
| 135 | emailAndPassword: { |
| 136 | enabled: true, |
| 137 | revokeSessionsOnPasswordReset: true, |
| 138 | }, |
| 139 | }); |
| 140 | ``` |
| 141 | |
| 142 | #### Password Requirements |
| 143 | |
| 144 | Password length limits (configurable): |
| 145 | |
| 146 | ```ts |
| 147 | export const auth = |
| 148 | emailAndPassword: { |
| 149 | enabled: true, |
| 150 | minPasswordLength: 12, |
| 151 | maxPasswordLength: 256, |
| 152 | }, |
| 153 | }); |
| 154 | ``` |
| 155 | |
| 156 | ### Sending the Password Reset |
| 157 | |
| 158 | Call `requestPasswordReset` to send the reset link. Triggers the `sendResetPassword` function from your config. |
| 159 | |
| 160 | ```ts |
| 161 | const data = await |
| 162 | body: { |
| 163 | email: "john.doe@example.com", // |
| 164 | redirectTo: "https://example.com/reset-password", |
| 165 | }, |
| 166 | }); |
| 167 | ``` |
| 168 | |
| 169 | Or authClient: |
| 170 | |
| 171 | ```ts |
| 172 | const { data, |
| 173 | email: "john.doe@example.com", // |
| 174 | redirectTo: "https://example.com/reset-password", |
| 175 | }); |
| 176 | ``` |
| 177 | |
| 178 | **Note**: While the `email |
| 179 | |
| 180 | ## Password Hashing |
| 181 | |
| 182 | Default: `scrypt` (Node.js native, no external dependencies). |
| 183 | |
| 184 | ### Custom Hashing Algorithm |
| 185 | |
| 186 | To use Argon2id or another algorithm, provide custom `hash` and `verify` functions: |
| 187 | |
| 188 | ```ts |
| 189 | import { betterAuth } |
| 190 | import { hash, verify |
| 191 | |
| 192 | const argon2Options: Options |
| 193 | memoryCost: 65536, // |
| 194 | timeCost: 3, // |
| 195 | parallelism: 4, // |
| 196 | outputLen: 32, // |
| 197 | algorithm: 2, // |
| 198 | }; |
| 199 | |
| 200 | export const auth = |
| 201 | emailAndPassword: { |
| 202 | enabled: true, |
| 203 | password: { |
| 204 | hash: (password) |
| 205 | verify: ({ password |
| 206 | verify(storedHash, password |
| 207 | }, |
| 208 | }, |
| 209 | }); |
| 210 | ``` |
| 211 | |
| 212 | **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. |