Skip to main content
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:
auth.ts
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:
sign-up.ts
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:
auth.ts
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:
sign-in.ts
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:
server.ts
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:
user-card.ts
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:
auth.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) => {
      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:
auth.ts
export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    requireEmailVerification: true,
  },
});
Handle the error on the client:
sign-in.ts
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

resend-verification.ts
import { authClient } from "@/lib/auth-client";

await authClient.sendVerificationEmail({
  email: "user@email.com",
  callbackURL: "/",
});

Password reset

Configure the server

Provide a sendResetPassword function in your emailAndPassword config:
auth.ts
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:
forgot-password.ts
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:
reset-password.ts
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:
change-password.ts
import { authClient } from "@/lib/auth-client";

await authClient.changePassword({
  currentPassword: "oldPassword1234",
  newPassword: "newPassword1234",
  revokeOtherSessions: true, // invalidate other active sessions
});

Configuration reference

OptionTypeDefaultDescription
enabledbooleanfalseEnable email and password authentication.
autoSignInbooleantrueAutomatically sign in after sign-up.
requireEmailVerificationbooleanfalseBlock sign-in until email is verified.
minPasswordLengthnumber8Minimum password length.
maxPasswordLengthnumber128Maximum password length.
disableSignUpbooleanfalseDisable email/password sign-up.
sendResetPasswordfunctionSend a password reset email.
onPasswordResetfunctionCallback after a successful password reset.
resetPasswordTokenExpiresInnumber3600Reset token lifetime in seconds.
revokeSessionsOnPasswordResetbooleanfalseRevoke all other sessions on password reset.