Skip to main content
Better Auth provides built-in support for:
  • Email and password
  • Social providers (Google, GitHub, Apple, Discord, and more)
You can also extend these with plugins such as username, magic link, passkey, and email OTP.

Email & password

Enable email and password authentication in your auth instance:
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({
  email,    // user email address
  password, // minimum 8 characters by default
  name,     // display name
  image,    // avatar URL (optional)
  callbackURL: "/dashboard", // redirect after email verification (optional)
}, {
  onRequest: (ctx) => {
    // show loading state
  },
  onSuccess: (ctx) => {
    // redirect to dashboard or show success message
  },
  onError: (ctx) => {
    alert(ctx.error.message);
  },
});
By default, users are automatically signed in after registration. To disable this:
auth.ts
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  emailAndPassword: {
    enabled: true,
    autoSignIn: false, // defaults to true
  },
});

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,
  password,
  callbackURL: "/dashboard",
  /**
   * Keep the session alive after the browser is closed.
   * @default true
   */
  rememberMe: false,
}, {
  // optional callbacks
});
Always call client methods from the client side. Do not call them from the server.

Server-side authentication

To authenticate a user from your server, use auth.api methods directly:
server.ts
import { auth } from "./auth";

const response = await auth.api.signInEmail({
  body: {
    email,
    password,
  },
  asResponse: true, // returns a Response object instead of data
});
If the server cannot return a Response object, you’ll need to manually parse and set cookies. For Next.js, Better Auth provides a plugin to handle this automatically.

Social sign-on

Better Auth supports Google, GitHub, Apple, Discord, and many more social providers. Configure the providers you need on your auth instance:
auth.ts
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
  },
});

Sign in with a social provider

Call signIn.social on the client:
sign-in.ts
import { authClient } from "@/lib/auth-client";

await authClient.signIn.social({
  /**
   * The social provider ID.
   * @example "github", "google", "apple"
   */
  provider: "github",
  /**
   * Redirect after the user authenticates with the provider.
   * @default "/"
   */
  callbackURL: "/dashboard",
  /**
   * Redirect if an error occurs during sign-in.
   */
  errorCallbackURL: "/error",
  /**
   * Redirect for newly registered users.
   */
  newUserCallbackURL: "/welcome",
  /**
   * Disable the automatic redirect to the provider.
   * @default false
   */
  disableRedirect: true,
});
You can also authenticate using an idToken or accessToken from the social provider instead of redirecting the user. See the social providers documentation for details.

Sign out

Call signOut on the client:
user-card.tsx
import { authClient } from "@/lib/auth-client";

await authClient.signOut();
Pass fetchOptions to redirect on success:
user-card.tsx
await authClient.signOut({
  fetchOptions: {
    onSuccess: () => {
      router.push("/login");
    },
  },
});

Session management

Once a user is signed in, you can access their session data from both the client and server.

Client side

useSession hook

Better Auth provides a useSession hook backed by nanostores. It keeps your UI in sync — any change to the session (such as signing out) is reflected immediately.
user.tsx
import { authClient } from "@/lib/auth-client";

export function User() {
  const {
    data: session,
    isPending, // loading state
    error,     // error object
    refetch,   // manually refetch
  } = authClient.useSession();

  return (
    // render session data
  );
}

getSession

If you prefer not to use the hook, call getSession directly:
import { authClient } from "@/lib/auth-client";

const { data: session, error } = await authClient.getSession();
This works with client-side data-fetching libraries like TanStack Query.

Server side

Pass the incoming request headers to auth.api.getSession:
server.ts
import { auth } from "./auth";
import { headers } from "next/headers";

const session = await auth.api.getSession({
  headers: await headers(),
});
For more details, see the session management documentation.

Using plugins

One of Better Auth’s key features is its plugin system — you can add complex auth functionality with just a few lines of code. Here’s an example using the two-factor authentication plugin:
1

Configure the server

Import the plugin and add it to the plugins array in your auth instance:
auth.ts
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";

export const auth = betterAuth({
  // ...rest of your options
  plugins: [
    twoFactor(),
  ],
});
Better Auth will now expose two-factor routes and methods on the server.
2

Migrate the database

Plugins often require additional tables. Run the CLI to apply the changes:
npx auth generate
Or apply the migration directly:
npx auth migrate
To add the schema manually, see the two-factor plugin documentation.
3

Configure the client

Add the matching client plugin to your auth client:
auth-client.ts
import { createAuthClient } from "better-auth/client";
import { twoFactorClient } from "better-auth/client/plugins";

const authClient = createAuthClient({
  plugins: [
    twoFactorClient({
      twoFactorPage: "/two-factor", // redirect here if 2FA is required
    }),
  ],
});
Two-factor methods are now available on the client:
profile.ts
import { authClient } from "./auth-client";

const enableTwoFactor = async () => {
  const data = await authClient.twoFactor.enable({
    password, // user's current password is required
  });
};

const disableTwoFactor = async () => {
  const data = await authClient.twoFactor.disable({
    password,
  });
};

const verifyTOTP = async () => {
  const data = await authClient.twoFactor.verifyTOTP({
    code: "123456", // code entered by the user
    /**
     * If trusted, the user won't need to pass 2FA again
     * on the same device.
     */
    trustDevice: true,
  });
};
4

Next steps

See the two-factor plugin documentation for the full list of methods and configuration options.