Email and password authentication is built into Better Auth. Enable it with a single config option, then use the client methods to sign users up, sign them in, and manage passwords.
If you prefer username-based login, see the username plugin. It extends email/password auth with username support.
Enable email and password
Set emailAndPassword.enabled to true in your betterAuth config:
import { betterAuth } from "better-auth";
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
},
});
Sign up
Call signUp.email on the client with the user’s details:
import { authClient } from "@/lib/auth-client";
const { data, error } = await authClient.signUp.email({
name: "John Doe",
email: "john.doe@example.com",
password: "password1234", // min 8 characters, max 128 by default
image: "https://example.com/avatar.png", // optional
callbackURL: "/dashboard", // redirect after email verification (optional)
}, {
onRequest: () => {
// show loading state
},
onSuccess: () => {
// redirect or show success
},
onError: (ctx) => {
alert(ctx.error.message);
},
});
Auto sign-in after sign-up
By default, users are automatically signed in after a successful sign-up. Disable this with autoSignIn: false:
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
autoSignIn: false, // defaults to true
},
});
When autoSignIn is false or requireEmailVerification is true, the sign-up endpoint returns a generic success response for existing emails to prevent email enumeration.
Sign in
Call signIn.email on the client:
import { authClient } from "@/lib/auth-client";
const { data, error } = await authClient.signIn.email({
email: "john.doe@example.com",
password: "password1234",
rememberMe: true, // keep session after browser closes (default: true)
callbackURL: "/dashboard", // optional redirect
});
Always call client methods from the client side. Never call them from the server.
Server-side sign-in
To authenticate a user on the server, use auth.api:
import { auth } from "./auth";
const response = await auth.api.signInEmail({
body: {
email: "john.doe@example.com",
password: "password1234",
},
asResponse: true, // returns a Response object so cookies are set
});
Sign out
Call signOut on the client:
import { authClient } from "@/lib/auth-client";
await authClient.signOut({
fetchOptions: {
onSuccess: () => {
router.push("/login");
},
},
});
Email verification
Provide a sendVerificationEmail function in your auth config. Better Auth calls it whenever a verification email needs to be sent:
import { betterAuth } from "better-auth";
import { sendEmail } from "./email"; // your email sending function
export const auth = betterAuth({
emailVerification: {
sendVerificationEmail: async ({ user, url, token }, request) => {
void sendEmail({
to: user.email,
subject: "Verify your email address",
text: `Click the link to verify your email: ${url}`,
});
},
},
});
Do not await the email sending call. Awaiting it can introduce timing attacks. On serverless platforms, use waitUntil to ensure the email is sent before the function exits.
Require email verification before sign-in
Set requireEmailVerification: true to block sign-in until the email is confirmed:
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
requireEmailVerification: true,
},
});
Handle the error on the client:
await authClient.signIn.email(
{ email: "user@example.com", password: "password" },
{
onError: (ctx) => {
if (ctx.error.status === 403) {
alert("Please verify your email address before signing in.");
}
},
}
);
Trigger verification manually
import { authClient } from "@/lib/auth-client";
await authClient.sendVerificationEmail({
email: "user@email.com",
callbackURL: "/",
});
Password reset
Provide a sendResetPassword function in your emailAndPassword config:
import { betterAuth } from "better-auth";
import { sendEmail } from "./email";
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
sendResetPassword: async ({ user, url, token }, request) => {
void sendEmail({
to: user.email,
subject: "Reset your password",
text: `Click the link to reset your password: ${url}`,
});
},
onPasswordReset: async ({ user }, request) => {
console.log(`Password for ${user.email} has been reset.`);
},
},
});
Request a password reset
Call requestPasswordReset on the client with the user’s email:
import { authClient } from "@/lib/auth-client";
await authClient.requestPasswordReset({
email: "john.doe@example.com",
redirectTo: "/reset-password", // your reset password page URL
});
When the user clicks the link in the email they are redirected to redirectTo with a ?token=... query parameter. If the token is invalid or expired, the redirect includes ?error=INVALID_TOKEN.
Reset the password
On your reset password page, read the token from the URL and call resetPassword:
import { authClient } from "@/lib/auth-client";
const token = new URLSearchParams(window.location.search).get("token");
if (token) {
const { data, error } = await authClient.resetPassword({
newPassword: "newPassword1234",
token,
});
}
Change password (authenticated)
Authenticated users can change their password directly:
import { authClient } from "@/lib/auth-client";
await authClient.changePassword({
currentPassword: "oldPassword1234",
newPassword: "newPassword1234",
revokeOtherSessions: true, // invalidate other active sessions
});
Configuration reference
| Option | Type | Default | Description |
|---|
enabled | boolean | false | Enable email and password authentication. |
autoSignIn | boolean | true | Automatically sign in after sign-up. |
requireEmailVerification | boolean | false | Block sign-in until email is verified. |
minPasswordLength | number | 8 | Minimum password length. |
maxPasswordLength | number | 128 | Maximum password length. |
disableSignUp | boolean | false | Disable email/password sign-up. |
sendResetPassword | function | — | Send a password reset email. |
onPasswordReset | function | — | Callback after a successful password reset. |
resetPasswordTokenExpiresIn | number | 3600 | Reset token lifetime in seconds. |
revokeSessionsOnPasswordReset | boolean | false | Revoke all other sessions on password reset. |