file: ./content/docs/basic-usage.mdx meta: { "title": "Basic Usage", "description": "Getting started with Better Auth" } Better Auth provides built-in authentication support for: * **Email and password** * **Social provider (Google, GitHub, Apple, and more)** But also can easily be extended using plugins, such as: [username](/docs/plugins/username), [magic link](/docs/plugins/magic-link), [passkey](/docs/plugins/passkey), [email-otp](/docs/plugins/email-otp), and more. ## Email & Password To enable email and password authentication: ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { // [!code highlight] enabled: true // [!code highlight] } // [!code highlight] }) ``` ### Sign Up To sign up a user you need to call the client method `signUp.email` with the user's information. ```ts title="sign-up.ts" import { authClient } from "@/lib/auth-client"; //import the auth client // [!code highlight] const { data, error } = await authClient.signUp.email({ email, // user email address password, // user password -> min 8 characters by default name, // user display name image, // user image url (optional) callbackURL: "/dashboard" // a url to redirect to after the user verifies their email (optional) }, { onRequest: (ctx) => { //show loading }, onSuccess: (ctx) => { //redirect to the dashboard or sign in page }, onError: (ctx) => { // display the error message alert(ctx.error.message); }, }); ``` By default, the users are automatically signed in after they successfully sign up. To disable this behavior you can set `autoSignIn` to `false`. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true, autoSignIn: false //defaults to true // [!code highlight] }, }) ``` ### Sign In To sign a user in, you can use the `signIn.email` function provided by the client. ```ts title="sign-in" const { data, error } = await authClient.signIn.email({ /** * The user email */ email, /** * The user password */ password, /** * a url to redirect to after the user verifies their email (optional) */ callbackURL: "/dashboard", /** * remember the user session after the browser is closed. * @default true */ rememberMe: false }, { //callbacks }) ``` Always invoke client methods from the client side. Don't call them from the server. ### Server-Side Authentication To authenticate a user on the server, you can use the `auth.api` methods. ```ts title="server.ts" import { auth } from "./auth"; // path to your Better Auth server instance 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. But for frameworks like Next.js we provide [a plugin](/docs/integrations/next#server-action-cookies) to handle this automatically ## Social Sign-On Better Auth supports multiple social providers, including Google, GitHub, Apple, Discord, and more. To use a social provider, you need to configure the ones you need in the `socialProviders` option on your `auth` object. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ socialProviders: { // [!code highlight] github: { // [!code highlight] clientId: process.env.GITHUB_CLIENT_ID!, // [!code highlight] clientSecret: process.env.GITHUB_CLIENT_SECRET!, // [!code highlight] } // [!code highlight] }, // [!code highlight] }) ``` ### Signin with social providers To sign in using a social provider you need to call `signIn.social`. It takes an object with the following properties: ```ts title="sign-in.ts" import { authClient } from "@/lib/auth-client"; //import the auth client // [!code highlight] await authClient.signIn.social({ /** * The social provider id * @example "github", "google", "apple" */ provider: "github", /** * a url to redirect after the user authenticates with the provider * @default "/" */ callbackURL: "/dashboard", /** * a url to redirect if an error occurs during the sign in process */ errorCallbackURL: "/error", /** * a url to redirect if the user is newly registered */ newUserCallbackURL: "/welcome", /** * disable the automatic redirect to the provider. * @default false */ disableRedirect: true, }); ``` You can also authenticate using `idToken` or `accessToken` from the social provider instead of redirecting the user to the provider's site. See social providers documentation for more details. ## Signout To signout a user, you can use the `signOut` function provided by the client. ```ts title="user-card.tsx" await authClient.signOut(); ``` you can pass `fetchOptions` to redirect onSuccess ```ts title="user-card.tsx" await authClient.signOut({ fetchOptions: { onSuccess: () => { router.push("/login"); // redirect to login page }, }, }); ``` ## Session Once a user is signed in, you'll want to access the user session. Better Auth allows you easily to access the session data from the server and client side. ### Client Side #### Use Session Better Auth provides a `useSession` hook to easily access session data on the client side. This hook is implemented using nanostore and has support for each supported framework and vanilla client, ensuring that any changes to the session (such as signing out) are immediately reflected in your UI. ```tsx title="user.tsx" import { authClient } from "@/lib/auth-client" // import the auth client // [!code highlight] export function User(){ const { // [!code highlight] data: session, // [!code highlight] isPending, //loading state // [!code highlight] error, //error object // [!code highlight] refetch //refetch the session } = authClient.useSession() // [!code highlight] return ( //... ) } ``` ```vue title="index.vue" ``` ```svelte title="user.svelte"

{$session.data?.user.email}

```
```ts title="user.svelte" import { authClient } from "~/lib/auth-client"; //import the auth client authClient.useSession.subscribe((value)=>{ //do something with the session // }) ``` ```tsx title="user.tsx" import { authClient } from "~/lib/auth-client"; // [!code highlight] export default function Home() { const session = authClient.useSession() // [!code highlight] return (
{JSON.stringify(session(), null, 2)}
); } ```
#### Get Session If you prefer not to use the hook, you can use the `getSession` method provided by the client. ```ts title="user.tsx" import { authClient } from "@/lib/auth-client" // import the auth client // [!code highlight] const { data: session, error } = await authClient.getSession() ``` You can also use it with client-side data-fetching libraries like [TanStack Query](https://tanstack.com/query/latest). ### Server Side The server provides a `session` object that you can use to access the session data. It requires request headers object to be passed to the `getSession` method. **Example: Using some popular frameworks** ```ts title="server.ts" import { auth } from "./auth"; // path to your Better Auth server instance import { headers } from "next/headers"; const session = await auth.api.getSession({ headers: await headers() // you need to pass the headers object. }) ``` ```ts title="route.ts" import { auth } from "lib/auth"; // path to your Better Auth server instance export async function loader({ request }: LoaderFunctionArgs) { const session = await auth.api.getSession({ headers: request.headers }) return json({ session }) } ``` ```astro title="index.astro" --- import { auth } from "./auth"; const session = await auth.api.getSession({ headers: Astro.request.headers, }); --- ``` ```ts title="+page.ts" import { auth } from "./auth"; export async function load({ request }) { const session = await auth.api.getSession({ headers: request.headers }) return { props: { session } } } ``` ```ts title="index.ts" import { auth } from "./auth"; const app = new Hono(); app.get("/path", async (c) => { const session = await auth.api.getSession({ headers: c.req.raw.headers }) }); ``` ```ts title="server/session.ts" import { auth } from "~/utils/auth"; export default defineEventHandler((event) => { const session = await auth.api.getSession({ headers: event.headers, }) }); ``` ```ts title="app/routes/api/index.ts" import { auth } from "./auth"; import { createAPIFileRoute } from "@tanstack/start/api"; export const APIRoute = createAPIFileRoute("/api/$")({ GET: async ({ request }) => { const session = await auth.api.getSession({ headers: request.headers }) }, }); ``` For more details check [session-management](/docs/concepts/session-management) documentation. ## Using Plugins One of the unique features of Better Auth is a plugins ecosystem. It allows you to add complex auth related functionality with small lines of code. Below is an example of how to add two factor authentication using two factor plugin. ### Server Configuration To add a plugin, you need to import the plugin and pass it to the `plugins` option of the auth instance. For example, to add two factor authentication, you can use the following code: ```ts title="auth.ts" import { betterAuth } from "better-auth" import { twoFactor } from "better-auth/plugins" // [!code highlight] export const auth = betterAuth({ //...rest of the options plugins: [ // [!code highlight] twoFactor() // [!code highlight] ] // [!code highlight] }) ``` now two factor related routes and method will be available on the server. ### Migrate Database After adding the plugin, you'll need to add the required tables to your database. You can do this by running the `migrate` command, or by using the `generate` command to create the schema and handle the migration manually. generating the schema: ```bash title="terminal" npx @better-auth/cli generate ``` using the `migrate` command: ```bash title="terminal" npx @better-auth/cli migrate ``` If you prefer adding the schema manually, you can check the schema required on the [two factor plugin](/docs/plugins/2fa#schema) documentation. ### Client Configuration Once we're done with the server, we need to add the plugin to the client. To do this, you need to import the plugin and pass it to the `plugins` option of the auth client. For example, to add two factor authentication, you can use the following code: ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client"; import { twoFactorClient } from "better-auth/client/plugins"; // [!code highlight] const authClient = createAuthClient({ plugins: [ // [!code highlight] twoFactorClient({ // [!code highlight] twoFactorPage: "/two-factor" // the page to redirect if a user need to verify 2nd factor // [!code highlight] }) // [!code highlight] ] // [!code highlight] }) ``` now two factor related methods will be available on the client. ```ts title="profile.ts" import { authClient } from "./auth-client" const enableTwoFactor = async() => { const data = await authClient.twoFactor.enable({ password // the user password is required }) // this will enable two factor } const disableTwoFactor = async() => { const data = await authClient.twoFactor.disable({ password // the user password is required }) // this will disable two factor } const signInWith2Factor = async() => { const data = await authClient.signIn.email({ //... }) //if the user has two factor enabled, it will redirect to the two factor page } const verifyTOTP = async() => { const data = await authClient.twoFactor.verifyTOTP({ code: "123456", // the code entered by the user /** * If the device is trusted, the user won't * need to pass 2FA again on the same device */ trustDevice: true }) } ``` Next step: See the the two factor plugin documentation. file: ./content/docs/comparison.mdx meta: { "title": "Comparison", "description": "Comparison of Better Auth versus over other auth libraries and services." } >

Comparison is the thief of joy. — Kanye West

Here are non detailed reasons why you may want to use Better Auth over other auth libraries and services. ### vs Other Auth Libraries * You need more features from your auth library. * We support almost all frameworks out of the box. * You need advanced features like two-factor authentication, multi-tenancy, multi-session, admin tools, rate limiting, etc. * You want to have more control over your auth flow. * You prefer more comprehensive documentation. * You like the idea of a plugin system to extend the library. * You want to use a library that is actively maintained and has a growing community. ### vs Self Hosted Auth Server * You want to keep your users in your main database (the same database as your app). * You need more control over your auth flow. * You don't want the hassle of self-hosting your auth server. * And there's a high possibility that we already cover the features you need. Additionally, you can always extend it using our plugin system. ### vs Managed Auth Services (Paid) * You don't want to give up user data to a third party. * You want a single source of truth for your users. * $$$ - You don't want to pay for every user. * You want to keep your users in your own database (the same database as your app). ### vs Roll Your Own Auth * You don't want to maintain a lot of code that isn't your core business. * You want to delegate the security to a community of people who probably care more than you do. * We probably cover most of the features you'll need, and you can always extend it using our plugin system. file: ./content/docs/installation.mdx meta: { "title": "Installation", "description": "Learn how to configure Better Auth in your project." } ### Install the Package Let's start by adding Better Auth to your project: ```bash npm install better-auth ``` ```bash pnpm add better-auth ``` ```bash yarn add better-auth ``` ```bash bun add better-auth ``` If you're using a separate client and server setup, make sure to install Better Auth in both parts of your project. ### Set Environment Variables Create a `.env` file in the root of your project and add the following environment variables: 1. **Secret Key** Random value used by the library for encryption and generating hashes. **You can generate one using the button below** or you can use something like openssl. ```txt title=".env" BETTER_AUTH_SECRET= ``` 2. **Set Base URL** ```txt title=".env" BETTER_AUTH_URL=http://localhost:3000 #Base URL of your app ``` ### Create A Better Auth Instance Create a file named `auth.ts` in one of these locations: * Project root * `lib/` folder * `utils/` folder You can also nest any of these folders under `src/`, `app/` or `server/` folder. (e.g. `src/lib/auth.ts`, `app/lib/auth.ts`). And in this file, import Better Auth and create your auth instance. Make sure to export the auth instance with the variable name `auth` or as a `default` export. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ //... }) ``` ### Configure Database Better Auth requires a database to store user data. You can easily configure Better Auth to use SQLite, PostgreSQL, or MySQL, with Kysely handling queries and migrations for these databases. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import Database from "better-sqlite3"; export const auth = betterAuth({ database: new Database("./sqlite.db"), }) ``` ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { Pool } from "pg"; export const auth = betterAuth({ database: new Pool({ // connection options }) }) ``` ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { createPool } from "mysql2/promise"; export const auth = betterAuth({ database: createPool({ // connection options }) }) ``` You can also provide any Kysely dialect or a Kysely instance to the `database` option. **Example with LibsqlDialect:** ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { LibsqlDialect } from "@libsql/kysely-libsql"; const dialect = new LibsqlDialect({ url: process.env.TURSO_DATABASE_URL || "", authToken: process.env.TURSO_AUTH_TOKEN || "", }) export const auth = betterAuth({ database: { dialect, type: "sqlite" } }); ``` **Adapters** If you prefer to use an ORM or if your database is not supported by Kysely, you can use one of the built-in adapters. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "sqlite", // or "mysql", "postgresql", ...etc }), }); ``` ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { db } from "@/db"; // your drizzle instance export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", // or "mysql", "sqlite" }) }); ``` ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { mongodbAdapter } from "better-auth/adapters/mongodb"; import { client } from "@/db"; // your mongodb client export const auth = betterAuth({ database: mongodbAdapter(client) }); ``` ### Create Database Tables Better Auth includes a CLI tool to help manage the schema required by the library. * **Generate**: This command generates an ORM schema or SQL migration file. If you're using Kysely, you can apply the migration directly with `migrate` command below. Use `generate` only if you plan to apply the migration manually. ```bash title="Terminal" npx @better-auth/cli generate ``` * **Migrate**: This command creates the required tables directly in the database. (Available only for the built-in Kysely adapter) ```bash title="Terminal" npx @better-auth/cli migrate ``` see the [CLI documentation](/docs/concepts/cli) for more information. If you instead want to create the schema manually, you can find the core schema required in the [database section](/docs/concepts/database#core-schema). ### Authentication Methods Configure the authentication methods you want to use. Better Auth comes with built-in support for email/password, and social sign-on providers. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ //...other options emailAndPassword: { // [!code highlight] enabled: true // [!code highlight] },// [!code highlight] socialProviders: { // [!code highlight] github: { // [!code highlight] clientId: process.env.GITHUB_CLIENT_ID, // [!code highlight] clientSecret: process.env.GITHUB_CLIENT_SECRET, // [!code highlight] } // [!code highlight] }, // [!code highlight] }); ``` You can use even more authentication methods like [passkey](/docs/plugins/passkey), [username](/docs/plugins/username), [magic link](/docs/plugins/magic-link) and more through plugins. ### Mount Handler To handle api requests, you need to set up a route handler on your server. Create a new file or route in your framework's designated catch-all route handler. This route should handle requests for the path `/api/auth/*` (unless you've configured a different base path). Better Auth supports any backend framework with standard Request and Response objects and offers helper functions for popular frameworks. ```ts title="/app/api/auth/[...all]/route.ts" import { auth } from "@/lib/auth"; // path to your auth file import { toNextJsHandler } from "better-auth/next-js"; export const { POST, GET } = toNextJsHandler(auth); ``` ```ts title="/server/api/auth/[...all].ts" import { auth } from "~/utils/auth"; // path to your auth file export default defineEventHandler((event) => { return auth.handler(toWebRequest(event)); }); ``` ```ts title="hooks.server.ts" import { auth } from "$lib/auth"; // path to your auth file import { svelteKitHandler } from "better-auth/svelte-kit"; export async function handle({ event, resolve }) { return svelteKitHandler({ event, resolve, auth }); } ``` ```ts title="/app/routes/api.auth.$.ts" import { auth } from '~/lib/auth.server' // Adjust the path as necessary import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node" export async function loader({ request }: LoaderFunctionArgs) { return auth.handler(request) } export async function action({ request }: ActionFunctionArgs) { return auth.handler(request) } ``` ```ts title="/routes/api/auth/*all.ts" import { auth } from "~/lib/auth"; // path to your auth file import { toSolidStartHandler } from "better-auth/solid-start"; export const { GET, POST } = toSolidStartHandler(auth); ``` ```ts title="src/index.ts" import { Hono } from "hono"; import { auth } from "./auth"; // path to your auth file import { serve } from "@hono/node-server"; import { cors } from "hono/cors"; const app = new Hono(); app.on(["POST", "GET"], "/api/auth/**", (c) => auth.handler(c.req.raw)); serve(app); ``` ```ts title="server.ts" import express from "express"; import { toNodeHandler } from "better-auth/node"; import { auth } from "./auth"; const app = express(); const port = 8000; app.all("/api/auth/*", toNodeHandler(auth)); // Mount express json middleware after Better Auth handler // or only apply it to routes that don't interact with Better Auth app.use(express.json()); app.listen(port, () => { console.log(`Better Auth app listening on port ${port}`); }); ``` This also works for any other node server framework like express, fastify, hapi, etc. Note that CommonJS (cjs) isn't supported. ```ts title="/pages/api/auth/[...all].ts" import type { APIRoute } from "astro"; import { auth } from "@/auth"; // path to your auth file export const GET: APIRoute = async (ctx) => { return auth.handler(ctx.request); }; export const POST: APIRoute = async (ctx) => { return auth.handler(ctx.request); }; ``` ```ts import { Elysia, Context } from "elysia"; import { auth } from "./auth"; const betterAuthView = (context: Context) => { const BETTER_AUTH_ACCEPT_METHODS = ["POST", "GET"] // validate request method if(BETTER_AUTH_ACCEPT_METHODS.includes(context.request.method)) { return auth.handler(context.request); } else { context.error(405) } } const app = new Elysia().all("/api/auth/*", betterAuthView).listen(3000); console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ); ``` ```ts title="app/routes/api/auth/$.ts" import { auth } from '~/lib/server/auth' import { createAPIFileRoute } from '@tanstack/start/api' export const APIRoute = createAPIFileRoute('/api/auth/$')({ GET: ({ request }) => { return auth.handler(request) }, POST: ({ request }) => { return auth.handler(request) }, }); ``` ```ts title="app/api/auth/[..all]+api.ts" import { auth } from '@/lib/server/auth'; // path to your auth file const handler = auth.handler; export { handler as GET, handler as POST }; ``` ### Create Client Instance The client-side library helps you interact with the auth server. Better Auth comes with a client for all the popular web frameworks, including vanilla JavaScript. 1. Import `createAuthClient` from the package for your framework (e.g., "better-auth/react" for React). 2. Call the function to create your client. 3. Pass the base url of your auth server. (If the auth server is running on the same domain as your client, you can skip this step.) If you're using a different base path other than `/api/auth` make sure to pass the whole url including the path. (e.g. `http://localhost:3000/custom-path/auth`) ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/client" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight] }) ``` ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/react" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight] }) ``` ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/vue" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight] }) ``` ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/svelte" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight] }) ``` ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/solid" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight] }) ``` Tip: You can also export specific methods if you prefer: ```ts export const { signIn, signUp, useSession } = createAuthClient() ``` ### 🎉 That's it! That's it! You're now ready to use better-auth in your application. Continue to [basic usage](/docs/basic-usage) to learn how to use the auth instance to sign in users. file: ./content/docs/introduction.mdx meta: { "title": "Introduction", "description": "Introduction to Better Auth." } Better Auth is a framework-agnostic authentication and authorization framework for TypeScript. It provides a comprehensive set of features out of the box and includes a plugin ecosystem that simplifies adding advanced functionalities. Whether you need 2FA, multi-tenancy, multi-session support, or even enterprise features like SSO, it lets you focus on building your application instead of reinventing the wheel. ## Why Better Auth? *Authentication in the TypeScript ecosystem has long been a half-solved problem. Other open-source libraries often require a lot of additional code for anything beyond basic authentication features. Rather than just pushing third-party services as the solution, I believe we can do better as a community—hence, Better Auth* ## Features Better Auth aims to be the most comprehensive auth library. It provides a wide range of features out of the box and allows you to extend it with plugins. Here are some of the features: ...and much more! file: ./content/docs/authentication/apple.mdx meta: { "title": "Apple", "description": "Apple provider setup and usage." } ### Get your OAuth credentials To use Apple sign in, you need a client ID and client secret. You can get them from the [Apple Developer Portal](https://developer.apple.com/account/resources/authkeys/list). Apple requires a little harder setup to get a client secret. You can use the guide below to get your client secret. Creating a client secret ### Configure the provider To configure the provider, you need to add it to the `socialProviders` option of the auth instance. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { apple: { // [!code highlight] clientId: process.env.APPLE_CLIENT_ID as string, // [!code highlight] clientSecret: process.env.APPLE_CLIENT_SECRET as string, // [!code highlight] // Optional appBundleIdentifier: process.env.APPLE_APP_BUNDLE_IDENTIFIER as string, // [!code highlight] }, // [!code highlight] }, }) ``` On native iOS, it doesn't use the service id but the app id (bundle id) as client id, so if using the service id as clientId in signIn.social() with idToken, it throws an error: JWTClaimValidationFailed: unexpected "aud" claim value. So you need to provide the appBundleIdentifier when you want to sign in with Apple using the ID Token. ## Usage ### Sign In with Apple To sign in with Apple, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `apple`. ```ts title="auth-client.ts" / import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "apple" }) } ``` ### Sign In with Apple With ID Token To sign in with Apple using the ID Token, you can use the `signIn.social` function to pass the ID Token. This is useful when you have the ID Token from Apple on the client-side and want to use it to sign in on the server. If id token is provided no redirection will happen, and the user will be signed in directly. ```ts title="auth-client.ts" await authClient.signIn.social({ provider: "apple", idToken: { token: // Apple ID Token, nonce: // Nonce (optional) accessToken: // Access Token (optional) } }) ``` file: ./content/docs/authentication/discord.mdx meta: { "title": "Discord", "description": "Discord provider setup and usage." } ### Get your Discord credentials To use Discord sign in, you need a client ID and client secret. You can get them from the [Discord Developer Portal](https://discord.com/developers/applications). Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/discord` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly. ### Configure the provider To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { discord: { // [!code highlight] clientId: process.env.DISCORD_CLIENT_ID as string, // [!code highlight] clientSecret: process.env.DISCORD_CLIENT_SECRET as string, // [!code highlight] }, // [!code highlight] }, }) ``` ### Sign In with Discord To sign in with Discord, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `discord`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "discord" }) } ``` file: ./content/docs/authentication/dropbox.mdx meta: { "title": "Dropbox", "description": "Dropbox provider setup and usage." } ### Get your Dropbox credentials To use Dropbox sign in, you need a client ID and client secret. You can get them from the [Dropbox Developer Portal](https://www.dropbox.com/developers). You can Allow "Implicit Grant & PKCE" for the application in the App Console. Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/dropbox` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly. If you need deeper dive into Dropbox Authentication, you can check out the [official documentation](https://developers.dropbox.com/oauth-guide). ### Configure the provider To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { dropbox: { // [!code highlight] clientId: process.env.DROPBOX_CLIENT_ID as string, // [!code highlight] clientSecret: process.env.DROPBOX_CLIENT_SECRET as string, // [!code highlight] }, // [!code highlight] }, }) ``` ### Sign In with Dropbox To sign in with Dropbox, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `dropbox`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "dropbox" }) } ``` file: ./content/docs/authentication/email-password.mdx meta: { "title": "Email & Password", "description": "Implementing email and password authentication with Better Auth." } Email and password authentication is a common method used by many applications. Better Auth provides a built-in email and password authenticator that you can easily integrate into your project. If you prefer username-based authentication, check out the{" "} username plugin. It extends the email and password authenticator with username support. ## Enable Email and Password To enable email and password authentication, you need to set the `emailAndPassword.enabled` option to `true` in the `auth` configuration. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ emailAndPassword: { // [!code highlight] enabled: true, // [!code highlight] }, // [!code highlight] }); ``` If it's not enabled, it'll not allow you to sign in or sign up with email and password. ## Usage ### Sign Up To sign a user up, you can use the `signUp.email` function provided by the client. The `signUp` function takes an object with the following properties: * `email`: The email address of the user. * `password`: The password of the user. It should be at least 8 characters long and max 32 by default. * `name`: The name of the user. * `image`: The image of the user. (optional) ```ts title="auth-client.ts" const { data, error } = await authClient.signUp.email({ email: "test@example.com", password: "password1234", name: "test", image: "https://example.com/image.png", }); ``` ### Sign In To sign a user in, you can use the `signIn.email` function provided by the client. The `signIn` function takes an object with the following properties: * `email`: The email address of the user. * `password`: The password of the user. * `rememberMe`: If false, the user will be signed out when the browser is closed. (optional) (default: true) * `callbackURL`: The URL to redirect to after the user signs in. (optional) ```ts title="auth-client.ts" const { data, error } = await authClient.signIn.email({ email: "test@example.com", password: "password1234", }); ``` ### Sign Out To sign a user out, you can use the `signOut` function provided by the client. ```ts title="auth-client.ts" await authClient.signOut(); ``` you can pass `fetchOptions` to redirect onSuccess ```ts title="auth-client.ts" await authClient.signOut({ fetchOptions: { onSuccess: () => { router.push("/login"); // redirect to login page }, }, }); ``` ### Email Verification To enable email verification, you need to pass a function that sends a verification email with a link. The `sendVerificationEmail` function takes a data object with the following properties: * `user`: The user object. * `url`: The url to send to the user which contains the token. * `token`: A verification token used to complete the email verification. and a `request` object as the second parameter. ```ts title="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) => { await sendEmail({ to: user.email, subject: "Verify your email address", text: `Click the link to verify your email: ${url}`, }); }, }, }); ``` On the client side you can use `sendVerificationEmail` function to send verification link to user. This will trigger the `sendVerificationEmail` function you provided in the `auth` configuration. Once the user clicks on the link in the email, if the token is valid, the user will be redirected to the URL provided in the `callbackURL` parameter. If the token is invalid, the user will be redirected to the URL provided in the `callbackURL` parameter with an error message in the query string `?error=invalid_token`. #### Require Email Verification If you enable require email verification, users must verify their email before they can log in. And every time a user tries to sign in, sendVerificationEmail is called. This only works if you have sendVerificationEmail implemented and if the user is trying to sign in with email and password. ```ts title="auth.ts" export const auth = betterAuth({ emailAndPassword: { requireEmailVerification: true, }, }); ``` If a user tries to sign in without verifying their email, you can handle the error and show a message to the user. ```ts title="auth-client.ts" await authClient.signIn.email( { email: "email@example.com", password: "password", }, { onError: (ctx) => { // Handle the error if (ctx.error.status === 403) { alert("Please verify your email address"); } //you can also show the original error message alert(ctx.error.message); }, } ); ``` #### Triggering manually Email Verification You can trigger the email verification manually by calling the `sendVerificationEmail` function. ```ts await authClient.sendVerificationEmail({ email: "user@email.com", callbackURL: "/", // The redirect URL after verification }); ``` ### Forget Password To allow users to reset a password first you need to provide `sendResetPassword` function to the email and password authenticator. The `sendResetPassword` function takes a data object with the following properties: * `user`: The user object. * `url`: The url to send to the user which contains the token. * `token`: A verification token used to complete the password reset. and a `request` object as the second parameter. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { sendEmail } from "./email"; // your email sending function export const auth = betterAuth({ emailAndPassword: { enabled: true, sendResetPassword: async ({user, url, token}, request) => { await sendEmail({ to: user.email, subject: "Reset your password", text: `Click the link to reset your password: ${url}`, }); }, }, }); ``` Once you configured your server you can call `forgetPassword` function to send reset password link to user. If the user exists, it will trigger the `sendResetPassword` function you provided in the auth config. It takes an object with the following properties: * `email`: The email address of the user. * `redirectTo`: The URL to redirect to after the user clicks on the link in the email. If the token is valid, the user will be redirected to this URL with the token in the query string. If the token is invalid, the user will be redirected to this URL with an error message in the query string `?error=invalid_token`. ```ts title="auth-client.ts" const { data, error } = await authClient.forgetPassword({ email: "test@example.com", redirectTo: "/reset-password", }); ``` When a user clicks on the link in the email, they will be redirected to the reset password page. You can add the reset password page to your app. Then you can use `resetPassword` function to reset the password. It takes an object with the following properties: * `newPassword`: The new password of the user. ```ts title="auth-client.ts" const token = new URLSearchParams(window.location.search).get("token"); if (!token) { // Handle the error } const { data, error } = await authClient.resetPassword({ newPassword: "password1234", token, }); ``` ### Update password This only works on server-side, and the following code may change over time. To set a password, you must hash it first: ```ts const ctx = await auth.$context; const hash = await ctx.password.hash("your-new-password"); ``` Then, to set the password: ```ts await ctx.internalAdapter.updatePassword("userId", hash) //(you can also use your orm directly) ``` ### Configuration **Password** Better Auth stores passwords inside the `account` table with `providerId` set to `credential`. **Password Hashing**: Better Auth uses `scrypt` to hash passwords. The `scrypt` algorithm is designed to be slow and memory-intensive to make it difficult for attackers to brute force passwords. OWASP recommends using `scrypt` if `argon2id` is not available. We decided to use `scrypt` because it's natively supported by Node.js. You can pass custom password hashing algorithm by setting `passwordHasher` option in the `auth` configuration. ```ts title="auth.ts" import { betterAuth } from "better-auth" import { scrypt } from "scrypt" export const auth = betterAuth({ //...rest of the options emailAndPassword: { password: { hash: // your custom password hashing function verify: // your custom password verification function } } }) ``` file: ./content/docs/authentication/facebook.mdx meta: { "title": "Facebook", "description": "Facebook provider setup and usage." } ### Get your Facebook credentials To use Facebook sign in, you need a client ID and client Secret. You can get them from the [Facebook Developer Portal](https://developers.facebook.com/). Select your app, navigate to **App Settings > Basic**, locate the following: * **App ID**: This is your `clientId` * **App Secret**: This is your `clientSecret`. Avoid exposing the `clientSecret` in client-side code (e.g., frontend apps) because it’s sensitive information. Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/facebook` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly. ### Configure the provider To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { facebook: { // [!code highlight] clientId: process.env.FACEBOOK_CLIENT_ID as string, // [!code highlight] clientSecret: process.env.FACEBOOK_CLIENT_SECRET as string, // [!code highlight] }, // [!code highlight] }, }) ``` ### Sign In with Facebook To sign in with Facebook, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `facebook`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/auth-client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "facebook" }) } ``` ## Additional Configuration ### Scopes By default, Facebook provides basic user information. If you need additional permissions, you can specify scopes in your auth configuration: ```ts title="auth.ts" export const auth = betterAuth({ socialProviders: { facebook: { clientId: process.env.FACEBOOK_CLIENT_ID as string, clientSecret: process.env.FACEBOOK_CLIENT_ID as string, scopes: ["email", "public_profile", "user_friends"], // Overwrites permissions fields: ["user_friends"], // Extending list of fields }, }, }) ``` Additional options: * `scopes`: Access basic account information (overwrites). * Default: `"email", "public_profile"` * `fields`: Extend list of fields to retrieve from the Facebook user profile (assignment). * Default: `"id", "name", "email", "picture"` ### Sign In with Facebook With ID or Access Token To sign in with Facebook using the ID Token, you can use the `signIn.social` function to pass the ID Token. This is useful when you have the ID Token from Facebook on the client-side and want to use it to sign in on the server. If id token is provided no redirection will happen, and the user will be signed in directly. For limited login, you need to pass `idToken.token`, for only `accessToken` you need to pass `idToken.accessToken` and `idToken.token` together because of (#1183)\[[https://github.com/better-auth/better-auth/issues/1183](https://github.com/better-auth/better-auth/issues/1183)]. ```ts title="auth-client.ts" const data = await authClient.signIn.social({ provider: "facebook", idToken: { // [!code highlight] ...(platform === 'ios' ? // [!code highlight] { token: idToken } // [!code highlight] : { token: accessToken, accessToken: accessToken }), // [!code highlight] }, }) ``` For a complete list of available permissions, refer to the [Permissions Reference](https://developers.facebook.com/docs/permissions). file: ./content/docs/authentication/github.mdx meta: { "title": "GitHub", "description": "GitHub provider setup and usage." } ### Get your GitHub credentials To use GitHub sign in, you need a client ID and client secret. You can get them from the [GitHub Developer Portal](https://github.com/settings/developers). Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/github` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly. ### Configure the provider To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { github: { // [!code highlight] clientId: process.env.GITHUB_CLIENT_ID as string, // [!code highlight] clientSecret: process.env.GITHUB_CLIENT_SECRET as string, // [!code highlight] }, // [!code highlight] }, }) ``` ### Sign In with GitHub To sign in with GitHub, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `github`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "github" }) } ``` file: ./content/docs/authentication/gitlab.mdx meta: { "title": "GitLab", "description": "GitLab provider setup and usage." } ### Get your GitLab credentials To use GitLab sign in, you need a client ID and client secret. [GitLab OAuth documentation](https://docs.gitlab.com/ee/api/oauth2.html). Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/gitlab` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly. ### Configure the provider To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { gitlab: { // [!code highlight] clientId: process.env.GITLAB_CLIENT_ID as string, // [!code highlight] clientSecret: process.env.GITLAB_CLIENT_SECRET as string, // [!code highlight] issuer: process.env.GITLAB_ISSUER as string, // [!code highlight] }, // [!code highlight] }, }) ``` ### Sign In with GitLab To sign in with GitLab, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `gitlab`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "gitlab" }) } ``` file: ./content/docs/authentication/google.mdx meta: { "title": "Google", "description": "Google provider setup and usage." } ### Get your Google credentials To use Google as a social provider, you need to get your Google credentials. You can get them by creating a new project in the [Google Cloud Console](https://console.cloud.google.com/apis/dashboard). In the Google Cloud Console > Credentials > Authorized redirect URIs, make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/google` for local development. For production, make sure to set the redirect URL as your application domain, e.g. `https://example.com/api/auth/callback/google`. If you change the base path of the auth routes, you should update the redirect URL accordingly. ### Configure the provider To configure the provider, you need to pass the `clientId` and `clientSecret` to `socialProviders.google` in your auth configuration. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { google: { // [!code highlight] clientId: process.env.GOOGLE_CLIENT_ID as string, // [!code highlight] clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, // [!code highlight] }, // [!code highlight] }, }) ``` ## Usage ### Sign In with Google To sign in with Google, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `google`. ```ts title="auth-client.ts" / import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "google" }) } ``` ### Sign In with Google With ID Token To sign in with Google using the ID Token, you can use the `signIn.social` function to pass the ID Token. This is useful when you have the ID Token from Google on the client-side and want to use it to sign in on the server. If id token is provided no redirection will happen, and the user will be signed in directly. ```ts title="auth-client.ts" const data = await authClient.signIn.social({ provider: "google", idToken: { token: // Google ID Token, accessToken: // Google Access Token } }) ``` If you want to use google one tap, you can use the [One Tap Plugin](/docs/plugins/one-tap) guide. file: ./content/docs/authentication/linkedin.mdx meta: { "title": "LinkedIn", "description": "LinkedIn Provider" } ### Get your LinkedIn credentials To use LinkedIn sign in, you need a client ID and client secret. You can get them from the [LinkedIn Developer Portal](https://www.linkedin.com/developers/). Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/linkedin` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly. In the LinkedIn portal under products you need the **Sign In with LinkedIn using OpenID Connect** product. There are some different Guides here: [Authorization Code Flow (3-legged OAuth) (Outdated)](https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow) [Sign In with LinkedIn using OpenID Connect](https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2?context=linkedin%2Fconsumer%2Fcontext) ### Configure the provider To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { linkedin: { // [!code highlight] clientId: process.env.LINKEDIN_CLIENT_ID as string, // [!code highlight] clientSecret: process.env.LINKEDIN_CLIENT_SECRET as string, // [!code highlight] }, // [!code highlight] }, }) ``` ### Sign In with LinkedIn To sign in with LinkedIn, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `linkedin`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "linkedin" }) } ``` file: ./content/docs/authentication/microsoft.mdx meta: { "title": "Microsoft", "description": "Microsoft provider setup and usage." } Enabling OAuth with Microsoft Azure Entra ID (formerly Active Directory) allows your users to sign in and sign up to your application with their Microsoft account. ### Get your Microsoft credentials To use Microsoft as a social provider, you need to get your Microsoft credentials. Which involves generating your own Client ID and Client Secret using your Microsoft Entra ID dashboard account. Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/microsoft` for local development. For production, you should change it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly. see the [Microsoft Entra ID documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) for more information. ### Configure the provider To configure the provider, you need to pass the `clientId` and `clientSecret` to `socialProviders.microsoft` in your auth configuration. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { microsoft: { // [!code highlight] clientId: process.env.MICROSOFT_CLIENT_ID as string, // [!code highlight] clientSecret: process.env.MICROSOFT_CLIENT_SECRET as string, // [!code highlight] // Optional tenantId: 'common', // [!code highlight] requireSelectAccount: true // [!code highlight] }, // [!code highlight] }, }) ``` ## Sign In with Microsoft To sign in with Microsoft, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `microsoft`. ```ts title="auth-client.ts" / import { createAuthClient } from "better-auth/client"; const authClient = createAuthClient(); const signIn = async () => { const data = await authClient.signIn.social({ provider: "microsoft", callbackURL: "/dashboard", //the url to redirect to after the sign in }); }; ``` file: ./content/docs/authentication/other-social-providers.mdx meta: { "title": "Other Social Providers", "description": "Other social providers setup and usage." } Better Auth providers out of the box support for the [Generic Oauth Plugin](/docs/plugins/generic-oauth) which allows you to use any social provider that implements the OAuth2 protocol or OpenID Connect (OIDC) flows. To use a provider that is not supported out of the box, you can use the [Generic Oauth Plugin](/docs/plugins/generic-oauth). ## Installation ### Add the plugin to your auth config To use the Generic OAuth plugin, add it to your auth config. ```ts title="auth.ts" import { betterAuth } from "better-auth" import { genericOAuth } from "better-auth/plugins" // [!code highlight] export const auth = betterAuth({ // ... other config options plugins: [ genericOAuth({ // [!code highlight] config: [ // [!code highlight] { // [!code highlight] providerId: "provider-id", // [!code highlight] clientId: "test-client-id", // [!code highlight] clientSecret: "test-client-secret", // [!code highlight] discoveryUrl: "https://auth.example.com/.well-known/openid-configuration", // [!code highlight] // ... other config options // [!code highlight] }, // [!code highlight] // Add more providers as needed // [!code highlight] ] // [!code highlight] }) // [!code highlight] ] }) ``` ### Add the client plugin Include the Generic OAuth client plugin in your authentication client instance. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" import { genericOAuthClient } from "better-auth/client/plugins" const authClient = createAuthClient({ plugins: [ genericOAuthClient() ] }) ``` Read more about installation and usage of the Generic Oauth plugin [here](/docs/plugins/generic-oauth#usage). ## Example usage ### Slack Example ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { genericOAuth } from "better-auth/plugins"; export const auth = betterAuth({ // ... other config options plugins: [ genericOAuth({ config: [ { providerId: "slack", clientId: process.env.SLACK_CLIENT_ID as string, clientSecret: process.env.SLACK_CLIENT_SECRET as string, authorizationUrl: "https://slack.com/oauth/v2/authorize", tokenUrl: "https://slack.com/api/oauth.v2.access", scopes: ["users:read", "users:read.email"], // and more... }, ], }), ], }); ``` ```ts title="sign-in.ts" const response = await authClient.signIn.oauth2({ providerId: "slack", callbackURL: "/dashboard", // the path to redirect to after the user is authenticated }); ``` ### Instagram Example ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { genericOAuth } from "better-auth/plugins"; export const auth = betterAuth({ // ... other config options plugins: [ genericOAuth({ config: [ { providerId: "instagram", clientId: process.env.INSTAGRAM_CLIENT_ID as string, clientSecret: process.env.INSTAGRAM_CLIENT_SECRET as string, authorizationUrl: "https://api.instagram.com/oauth/authorize", tokenUrl: "https://api.instagram.com/oauth/access_token", scopes: ["user_profile", "user_media"], }, ], }), ], }); ``` ```ts title="sign-in.ts" const response = await authClient.signIn.oauth2({ providerId: "instagram", callbackURL: "/dashboard", // the path to redirect to after the user is authenticated }); ``` ### Coinbase Example ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { genericOAuth } from "better-auth/plugins"; export const auth = betterAuth({ // ... other config options plugins: [ genericOAuth({ config: [ { providerId: "coinbase", clientId: process.env.COINBASE_CLIENT_ID as string, clientSecret: process.env.COINBASE_CLIENT_SECRET as string, authorizationUrl: "https://www.coinbase.com/oauth/authorize", tokenUrl: "https://api.coinbase.com/oauth/token", scopes: ["wallet:user:read"], // and more... }, ], }), ], }); ``` ```ts title="sign-in.ts" const response = await authClient.signIn.oauth2({ providerId: "coinbase", callbackURL: "/dashboard", // the path to redirect to after the user is authenticated }); ``` file: ./content/docs/authentication/reddit.mdx meta: { "title": "Reddit", "description": "Reddit provider setup and usage." } ### Get your Reddit Credentials To use Reddit sign in, you need a client ID and client secret. You can get them from the [Reddit Developer Portal](https://www.reddit.com/prefs/apps). 1. Click "Create App" or "Create Another App" 2. Select "web app" as the application type 3. Set the redirect URL to `http://localhost:3000/api/auth/callback/reddit` for local development 4. For production, set it to your application's domain (e.g. `https://example.com/api/auth/callback/reddit`) 5. After creating the app, you'll get the client ID (under the app name) and client secret If you change the base path of the auth routes, make sure to update the redirect URL accordingly. ### Configure the provider To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { reddit: { clientId: process.env.REDDIT_CLIENT_ID as string, clientSecret: process.env.REDDIT_CLIENT_SECRET as string, }, }, }) ``` ### Sign In with Reddit To sign in with Reddit, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `reddit`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "reddit" }) } ``` ## Additional Configuration ### Scopes By default, Reddit provides basic user information. If you need additional permissions, you can specify scopes in your auth configuration: ```ts title="auth.ts" export const auth = betterAuth({ socialProviders: { reddit: { clientId: process.env.REDDIT_CLIENT_ID as string, clientSecret: process.env.REDDIT_CLIENT_SECRET as string, duration: "permanent", scopes: ["identity", "read", "submit"] // Add required scopes }, }, }) ``` Common Reddit scopes include: * `identity`: Access basic account information * `read`: Access posts and comments * `submit`: Submit posts and comments * `subscribe`: Manage subreddit subscriptions * `history`: Access voting history For a complete list of available scopes, refer to the [Reddit OAuth2 documentation](https://www.reddit.com/dev/api/oauth). file: ./content/docs/authentication/spotify.mdx meta: { "title": "Spotify", "description": "Spotify provider setup and usage." } ### Get your Spotify Credentials To use Spotify sign in, you need a client ID and client secret. You can get them from the [Spotify Developer Portal](https://developer.spotify.com/dashboard/applications). Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/spotify` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly. ### Configure the provider To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { spotify: { // [!code highlight] clientId: process.env.SPOTIFY_CLIENT_ID as string, // [!code highlight] clientSecret: process.env.SPOTIFY_CLIENT_SECRET as string, // [!code highlight] }, // [!code highlight] }, }) ``` ### Sign In with Spotify To sign in with Spotify, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `spotify`. ```ts title="auth-client.ts" / import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "spotify" }) } ``` file: ./content/docs/authentication/twitch.mdx meta: { "title": "Twitch", "description": "Twitch provider setup and usage." } ### Get your Twitch Credentials To use Twitch sign in, you need a client ID and client secret. You can get them from the [Twitch Developer Portal](https://dev.twitch.tv/console/apps). Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/twitch` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly. ### Configure the provider To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { twitch: { // [!code highlight] clientId: process.env.TWITCH_CLIENT_ID as string, // [!code highlight] clientSecret: process.env.TWITCH_CLIENT_SECRET as string, // [!code highlight] }, // [!code highlight] } }) ``` ### Sign In with Twitch To sign in with Twitch, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `twitch`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "twitch" }) } ``` file: ./content/docs/authentication/twitter.mdx meta: { "title": "Twitter (X)", "description": "Twitter provider setup and usage." } ### Get your Twitter Credentials Get your Twitter credentials from the [Twitter Developer Portal](https://developer.twitter.com/en/portal/dashboard). Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/twitter` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly. { /* If twitter doesn't return the email address, the authentication won't be successful. Make sure to ask for the email address when you create the Twitter app. */} Twitter API v2 does not provide email addresses. As a workaround, the user's `email` field uses the `username` value instead. ### Configure the provider To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { twitter: { // [!code highlight] clientId: process.env.TWITTER_CLIENT_ID, // [!code highlight] clientSecret: process.env.TWITTER_CLIENT_SECRET, // [!code highlight] }, // [!code highlight] }, }) ``` ### Sign In with Twitter To sign in with Twitter, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `twitter`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "twitter" }) } ``` file: ./content/docs/concepts/api.mdx meta: { "title": "API", "description": "Better Auth API." } When you create a new Better Auth instance, it gives you an `api` object. This object provides functions to interact with the server while your code is running server-side. You can use these functions to call any API endpoint on the server. Any endpoint added to Better Auth, whether from plugins or the core, will be accessible through the `api` object. ## Calling API Endpoints on the Server To call an API endpoint on the server, import your `auth` instance and call the endpoint using the `api` object. ```ts title="server.ts" import { betterAuth } from "better-auth"; import { headers } from "next/headers"; export const auth = betterAuth({ //... }) // calling get session on the server await auth.api.getSession({ headers: headers() }) ``` Unlike the client, the server needs the values to be passed as an object with the key `body` for the body, `headers` for the headers, and `query` for the query. ```ts title="server.ts" await auth.api.signInEmail({ body: { email: "", password: "" } }) ``` Better auth API endpoints are built on top of [better-call](https://github.com/bekacru/better-call), a tiny web framework that lets you call REST API endpoints as if they were regular functions and allows us to easily infer client types from the server. ### Getting the `Response` Object When you invoke an API endpoint on the server, it will return a standard JavaScript object or array directly. To get the `Response` object instead, you can use the `asResponse` option. ```ts title="server.ts" const response = await auth.api.signInEmail({ body: { email: "", password: "" }, asResponse: true }) ``` ### Error Handling When you call an API endpoint in the server, it will throw an error if the request fails. You can catch the error and handle it as you see fit. The error instance is an instance of `APIError`. ```ts title="server.ts" import { APIError } from "better-auth/api"; try { await auth.api.signInEmail({ body: { email: "", password: "" } }) } catch (error) { if (error instanceof APIError) { console.log(error.message, error.status) } } ``` file: ./content/docs/concepts/cli.mdx meta: { "title": "CLI", "description": "Built in CLI for managing your project." } Better Auth comes with a built-in CLI to help you manage the database schema needed for both core functionality and plugins. ## Generate The `generate` command creates the schema required by Better Auth. If you're using a database adapter like Prisma or Drizzle, this command will generate the right schema for your ORM. If you're using the built-in Kysely adapter, it will generate an SQL file you can run directly on your database. ```bash title="Terminal" npx @better-auth/cli@latest generate ``` ### Options * `--output` - Where to save the generated schema. For Prisma, it will be saved in prisma/schema.prisma. For Drizzle, it goes to schema.ts in your project root. For Kysely, it’s an SQL file saved as schema.sql in your project root. * `--config` - The path to your Better Auth config file. By default, the CLI will search for a better-auth.ts file in **./**, **./utils**, **./lib**, or any of these directories under `src` directory. * `--y` - Skip the confirmation prompt and generate the schema directly. ## Migrate The migrate command applies the Better Auth schema directly to your database. This is available if you’re using the built-in Kysely adapter. ```bash title="Terminal" npx @better-auth/cli@latest migrate ``` ### Options * `--config` - The path to your Better Auth config file. By default, the CLI will search for a better-auth.ts file in **./**, **./utils**, **./lib**, or any of these directories under `src` directory. * `--y` - Skip the confirmation prompt and apply the schema directly. ## Common Issues **Error: Cannot find module X** If you see this error, it means the CLI can’t resolve imported modules in your Better Auth config file. We're working on a fix for many of these issues, but in the meantime, you can try the following: * Remove any import aliases in your config file and use relative paths instead. After running the CLI, you can revert to using aliases. ## Secret The CLI also provides a way to generate a secret key for your Better Auth instance. ```bash title="Terminal" npx @better-auth/cli@latest secret ``` file: ./content/docs/concepts/client.mdx meta: { "title": "Client", "description": "Better Auth client library for authentication." } Better Auth offers a client library compatible with popular frontend frameworks like React, Vue, Svelte, and more. This client library includes a set of functions for interacting with the Better Auth server. Each framework's client library is built on top of a core client library that is framework-agnostic, so that all methods and hooks are consistently available across all client libraries. ## Installation If you haven't already, install better-auth. ```bash npm i better-auth ``` ```bash pnpm add better-auth ``` ```bash yarn add better-auth ``` ```bash bun add better-auth ``` ## Create Client Instance Import `createAuthClient` from the package for your framework (e.g., "better-auth/react" for React). Call the function to create your client. Pass the base URL of your auth server. If the auth server is running on the same domain as your client, you can skip this step. If you're using a different base path other than `/api/auth`, make sure to pass the whole URL, including the path. (e.g., `http://localhost:3000/custom-path/auth`) ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/client" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight] }) ``` ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/react" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight] }) ``` ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/vue" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight] }) ``` ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/svelte" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight] }) ``` ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/solid" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight] }) ``` ## Usage Once you've created your client instance, you can use the client to interact with the Better Auth server. The client provides a set of functions by default and they can be extended with plugins. **Example: Sign In** ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() await authClient.signIn.email({ email: "test@user.com", password: "password1234" }) ``` ### Hooks On top of normal methods, the client provides hooks to easily access different reactive data. Every hook is available in the root object of the client and they all start with `use`. **Example: useSession** ```tsx title="user.tsx" //make sure you're using the react client import { createAuthClient } from "better-auth/react" const { useSession } = createAuthClient() // [!code highlight] export function User() { const { data: session, isPending, //loading state error, //error object refetch //refetch the session } = useSession() return ( //... ) } ``` ```vue title="user.vue" ``` ```svelte title="user.svelte"
{#if $session}

{$session?.data?.user.name}

{$session?.data?.user.email}

{:else} {/if}
```
```tsx title="user.tsx" import { client } from "~/lib/client"; import { Show } from 'solid-js'; export default function Home() { const session = client.useSession() return ( Log in} > ); } ```
### Fetch Options The client uses a library called [better fetch](https://better-fetch.vercel.app) to make requests to the server. Better fetch is a wrapper around the native fetch API that provides a more convenient way to make requests. It's created by the same team behind Better Auth and is designed to work seamlessly with it. You can pass any default fetch options to the client by passing `fetchOptions` object to the `createAuthClient`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient({ fetchOptions: { //any better-fetch options }, }) ``` You can also pass fetch options to most of the client functions. Either as the second argument or as a property in the object. ```ts title="auth-client.ts" await authClient.signIn.email({ email: "email@email.com", password: "password1234", }, { onSuccess(ctx) { // } }) //or await authClient.signIn.email({ email: "email@email.com", password: "password1234", fetchOptions: { onSuccess(ctx) { // } }, }) ``` ### Handling Errors Most of the client functions return a response object with the following properties: * `data`: The response data. * `error`: The error object if there was an error. the error object contains the following properties: * `message`: The error message. (e.g., "Invalid email or password") * `status`: The HTTP status code. * `statusText`: The HTTP status text. ```ts title="auth-client.ts" const { data, error } = await authClient.signIn.email({ email: "email@email.com", password: "password1234" }) if (error) { //handle error } ``` If the actions accepts a `fetchOptions` option, you can pass `onError` callback to handle errors. ```ts title="auth-client.ts" await authClient.signIn.email({ email: "email@email.com", password: "password1234", }, { onError(ctx) { //handle error } }) //or await authClient.signIn.email({ email: "email@email.com", password: "password1234", fetchOptions: { onError(ctx) { //handle error } } }) ``` Hooks like `useSession` also return an error object if there was an error fetching the session. On top of that, they also return a `isPending` property to indicate if the request is still pending. ```ts title="auth-client.ts" const { data, error, isPending } = useSession() if (error) { //handle error } ``` #### Error Codes The client instance contains $ERROR\_CODES object that contains all the error codes returned by the server. You can use this to handle error translations or custom error messages. ```ts title="auth-client.ts" const authClient = createAuthClient(); type ErrorTypes = Partial< Record< keyof typeof client.$ERROR_CODES, { en: string; es: string; } > >; const errorCodes = { USER_ALREADY_EXISTS: { en: "user already registered", es: "usuario ya registrada", }, } satisfies ErrorTypes; const getErrorMessage = (code: string, lang: "en" | "es") => { if (code in errorCodes) { return errorCodes[code as keyof typeof errorCodes][lang]; } return ""; }; const { error } = await authClient.signUp.email({ email: "user@email.com", password: "password", name: "User", }); if(error?.code){ alert(getErrorMessage(error.code), "en"); } ``` ### Plugins You can extend the client with plugins to add more functionality. Plugins can add new functions to the client or modify existing ones. **Example: Magic Link Plugin** ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" import { magicLinkClient } from "better-auth/client/plugins" const authClient = createAuthClient({ plugins: [ magicLinkClient() ] }) ``` once you've added the plugin, you can use the new functions provided by the plugin. ```ts title="auth-client.ts" await authClient.signIn.magicLink({ email: "test@email.com" }) ``` file: ./content/docs/concepts/cookies.mdx meta: { "title": "Cookies", "description": "Learn how cookies are used in Better Auth." } Cookies are used to store data such as session tokens, OAuth state, and more. All cookies are signed using the `secret` key provided in the auth options. ### Cookie Prefix Better Auth cookies will follow `${prefix}.${cookie_name}` format by default. The prefix will be "better-auth" by default. You can change the prefix by setting `cookiePrefix` in the `advanced` object of the auth options. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ advanced: { cookiePrefix: "my-app" } }) ``` ### Custom Cookies All cookies are `httpOnly` and `secure` if the server is running in production mode. If you want to set custom cookie names and attributes, you can do so by setting `cookieOptions` in the `advanced` object of the auth options. By default, Better Auth uses the following cookies: * `session_token` to store the session token * `session_data` to store the session data if cookie cache is enabled * `dont_remember` to store the `dont_remember` flag if remember me is disabled Plugins may also use cookies to store data. For example, the Two Factor Authentication plugin uses the `two_factor` cookie to store the two-factor authentication state. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ advanced: { cookies: { session_token: { name: "custom_session_token", attributes: { // Set custom cookie attributes } }, } } }) ``` ### Cross Subdomain Cookies Sometimes you may need to share cookies across subdomains. For example, if you have `app.example.com` and `example.com`, and if you authenticate on `example.com`, you may want to access the same session on `app.example.com`. By default, cookies are not shared between subdomains. However, if you need to access the same session across different subdomains, you can enable cross-subdomain cookies by configuring `crossSubDomainCookies` and `defaultCookieAttributes` in the `advanced` object of the auth options. The leading period of the `domain` attribute broadens the cookie's scope beyond your main domain, making it accessible across all subdomains. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ advanced: { crossSubDomainCookies: { enabled: true, domain: ".example.com", // Domain with a leading period }, defaultCookieAttributes: { secure: true, httpOnly: true, sameSite: "none", // Allows CORS-based cookie sharing across subdomains }, }, trustedOrigins: [ 'https://example.com', 'https://app1.example.com', 'https://app2.example.com', ], }) ``` Setting your `sameSite` cookie attribute to `none` makes you vulnerable to CSRF attacks. To mitigate risks, configure `trustedOrigins` with an array of authorized origins to allow. ### Secure Cookies By default, cookies are secure only when the server is running in production mode. You can force cookies to be always secure by setting `useSecureCookies` to `true` in the `advanced` object in the auth options. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ advanced: { useSecureCookies: true } }) ``` file: ./content/docs/concepts/database.mdx meta: { "title": "Database", "description": "Learn how to use a database with Better Auth." } Better Auth requires a database connection to store data. It comes with a query builder called Kysely to manage and query your database. The database will be used to store data such as users, sessions, and more. Plugins can also define their own database tables to store data. You can pass a database connection to Better Auth by passing a supported database instance, a dialect instance or a Kysely instance in the database options. **Example: Sqlite** ```ts title="auth.ts" import { betterAuth } from "better-auth" import Database from "better-sqlite3" export const auth = betterAuth({ database: new Database("database.sqlite") }) ``` **Example: Postgres** ```ts title="auth.ts" import { betterAuth } from "better-auth" import { Pool } from "pg" export const auth = betterAuth({ database: new Pool({ connectionString: "postgres://user:password@localhost:5432/database" }) }) ``` **Example: MySQL** ```ts title="auth.ts" import { betterAuth } from "better-auth" import { createPool } from "mysql2/promise" export const auth = betterAuth({ database: createPool({ host: "localhost", user: "root", password: "password", database: "database" }) }) ``` **Example: Custom Dialect using libSQL** ```ts title="auth.ts" import { betterAuth } from "better-auth" import { LibsqlDialect } from "@libsql/kysely-libsql"; export const auth = betterAuth({ database: { dialect: new LibsqlDialect({ url: process.env.TURSO_DATABASE_URL || "", authToken: process.env.TURSO_AUTH_TOKEN || "", }), type: "sqlite" }, }) ``` See Kysely Dialects for more dialects supported by Kysely. **Example: Custom Kysely Instance** ```ts title="auth.ts" import { betterAuth } from "better-auth" import { db } from "./db" export const auth = betterAuth({ database: { db: db, type: "sqlite" // or "mysql", "postgres" or "mssql" } }) ``` ## Using Adapters If your database is managed by an ORM like Prisma or Drizzle, you can use the corresponding adapter to connect to the database. Better Auth comes with built-in adapters for Prisma and Drizzle. You can pass the adapter to the `database` object in the auth options. ### Prisma Adapter The Prisma adapter expects a prisma client instance and a provider key that specifies the database provider to use. The provider key can be `sqlite`, `postgres`, `mysql`, or any other supported by prisma. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "sqlite" }) }) ``` ### Drizzle adapter The Drizzle adapter expects a drizzle client instance and a provider key that specifies the database provider to use. The provider key can be `sqlite`, `pg` or `mysql`. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { db } from "./drizzle"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "sqlite", // or "pg" or "mysql" }) }) ``` #### Mapping Schema The Drizzle adapter expects the schema you define to match the table names. For example, if your Drizzle schema maps the `user` table to `users`, you need to manually pass the schema and map it to the user table. ```ts import { betterAuth } from "better-auth"; import { db } from "./drizzle"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { schema } from "./schema"; export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "sqlite", // or "pg" or "mysql" schema: { ...schema, user: schema.users, }, //if all of them are just using plural form, you can just pass the option below usePlural: true }) }) ``` ### MongoDB Adapter The MongoDB adapter expects a mongodb client instance and a database name. The adapter will create a new database with the provided name if it doesn't exist. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { MongoClient } from "mongodb"; import { mongodbAdapter } from "better-auth/adapters/mongodb"; const client = new MongoClient("mongodb://localhost:27017/database"); const db = client.db() export const auth = betterAuth({ database: mongodbAdapter(db) }) ``` ## CLI Better Auth comes with a CLI tool to manage database migrations and generate schema. ### Running Migrations The cli checks your database and prompts you to add missing tables or update existing ones with new columns. This is only supported for the built-in Kysely adapter. For other adapters, you can use the `generate` command to create the schema and handle the migration through your ORM. ```bash npx @better-auth/cli migrate ``` ### Generating Schema Better Auth also provides a `generate` command to generate the schema required by Better Auth. The `generate` command creates the schema required by Better Auth. If you're using a database adapter like Prisma or Drizzle, this command will generate the right schema for your ORM. If you're using the built-in Kysely adapter, it will generate an SQL file you can run directly on your database. ```bash npx @better-auth/cli generate ``` See the [CLI](/docs/concepts/cli) documentation for more information on the CLI. If you prefer adding tables manually, you can do that as well. The core schema required by Better Auth is described below and you can find additional schema required by plugins in the plugin documentation. ## Secondary Storage Secondary storage in Better Auth allows you to use key-value stores for managing session data, rate limiting counters, etc. This can be useful when you want to offload the storage of this intensive records to a high performance storage or even RAM. ### Implementation To use secondary storage, implement the `SecondaryStorage` interface: ```typescript interface SecondaryStorage { get: (key: string) => Promise set: ( key: string, value: string, ttl?: number, ) => Promise; delete: (key: string) => Promise; } ``` Then, provide your implementation to the `betterAuth` function: ```typescript betterAuth({ // ... other options secondaryStorage: { // Your implementation here } }) ``` **Example: Redis Implementation** Here's a basic example using Redis: ```typescript import { createClient } from "redis"; import { betterAuth } from "better-auth"; const redis = createClient(); await redis.connect(); export const auth = betterAuth({ // ... other options secondaryStorage: { get: async (key) => { const value = await redis.get(key); return value ? JSON.stringify(value) : null; }, set: async (key, value, ttl) => { if (ttl) await redis.set(key, value, { EX: ttl }); // or for ioredis: // if (ttl) await redis.set(key, value, 'EX', ttl) else await redis.set(key, value); }, delete: async (key) => { await redis.del(key); } } }); ``` This implementation allows Better Auth to use Redis for storing session data and rate limiting counters. You can also add prefixes to the keys names. ## Core Schema Better Auth requires the following tables to be present in the database. The types are in `typescript` format. You can use corresponding types in your database. ### User Table Name: `user` ### Session Table Name: `session` ### Account Table Name: `account` ### Verification Table Name: `verification` ## Custom Tables Better Auth allows you to customize the table names and column names for the core schema. You can also extend the core schema by adding additional fields to the user and session tables. ### Custom Table Names You can customize the table names and column names for the core schema by using the `modelName` and `fields` properties in your auth config: ```ts title="auth.ts" export const auth = betterAuth({ user: { modelName: "users", fields: { name: "full_name", email: "email_address" } }, session: { modelName: "user_sessions", fields: { userId: "user_id" } } }) ``` Type inference in your code will still use the original field names (e.g., `user.name`, not `user.full_name`). To customize table names and column name for plugins, you can use the `schema` property in the plugin config: ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { twoFactor } from "better-auth/plugins"; export const auth = betterAuth({ plugins: [ twoFactor({ schema: { user: { fields: { twoFactorEnabled: "two_factor_enabled", secret: "two_factor_secret", }, }, }, }), ], }); ``` ### Extending Core Schema Better Auth provides a type-safe way to extend the `user` and `session` schemas. You can add custom fields to your auth config, and the CLI will automatically update the database schema. These additional fields will be properly inferred in functions like `useSession`, `signUp.email`, and other endpoints that work with user or session objects. To add custom fields, use the `additionalFields` property in the `user` or `session` object of your auth config. The `additionalFields` object uses field names as keys, with each value being a `FieldAttributes` object containing: * `type`: The data type of the field (e.g., "string", "number", "boolean"). * `required`: A boolean indicating if the field is mandatory. * `defaultValue`: The default value for the field (note: this only applies in the JavaScript layer; in the database, the field will be optional). * `input`: This determines whether a value can be provided when creating a new record (default: `true`). If there are additional fields, like `role`, that should not be provided by the user during signup, you can set this to `false`. Here's an example of how to extend the user schema with additional fields: ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ user: { additionalFields: { role: { type: "string", required: false, defaultValue: "user", input: false // don't allow user to set role }, lang: { type: "string", required: false, defaultValue: "en", } } } }) ``` Now you can access the additional fields in your application logic. ```ts //on signup const res = await auth.api.signUpEmail({ email: "test@example.com", password: "password", name: "John Doe", lang: "fr" }) //user object res.user.role // > "admin" res.user.lang // > "fr" ``` See the [Typescript](/docs/concepts/typescript#inferring-additional-fields-on-client) documentation for more information on how to infer additional fields on the client side. If you're using social / OAuth providers, you may want to provide `mapProfileToUser` to map the profile data to the user object. So, you can populate additional fields from the provider's profile. **Example: Mapping Profile to User For `firstName` and `lastName`** ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ socialProviders: { github: { clientId: "YOUR_GITHUB_CLIENT_ID", clientSecret: "YOUR_GITHUB_CLIENT_SECRET", mapProfileToUser: (profile) => { return { firstName: profile.name.split(" ")[0], lastName: profile.name.split(" ")[1], } } }, google: { clientId: "YOUR_GOOGLE_CLIENT_ID", clientSecret: "YOUR_GOOGLE_CLIENT_SECRET", mapProfileToUser: (profile) => { return { firstName: profile.given_name, lastName: profile.family_name, } } } } }) ``` ### ID Generation Better Auth by default will generate unique IDs for users, sessions, and other entities. If you want to customize how IDs are generated, you can configure this in the `advanced` object in your auth config. You can also disable ID generation by setting the `generateId` option to `false`. This will assume your database will generate the ID automatically. **Example: Automatic Database IDs** ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { db } from "./db"; export const auth = betterAuth({ database: { db: db }, advanced: { generateId: false, }, }) ``` ### Database Hooks Database hooks allow you to define custom logic that can be executed during the lifecycle of core database operations in Better Auth. You can create hooks for the following models: **user**, **session**, and **account**. There are two types of hooks you can define: #### 1. Before Hook * **Purpose**: This hook is called before the respective entity (user, session, or account) is created or updated. * **Behavior**: If the hook returns `false`, the operation will be aborted. And If it returns a data object, it'll replace the orginal payload. #### 2. After Hook * **Purpose**: This hook is called after the respective entity is created or updated. * **Behavior**: You can perform additional actions or modifications after the entity has been successfully created or updated. **Example Usage** ```typescript title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ databaseHooks: { user: { create: { before: async (user) => { // Modify the user object before it is created return { data: { ...user, firstName: user.name.split(" ")[0], lastName: user.name.split(" ")[1] } } }, after: async (user) => { //perform additional actions, like creating a stripe customer }, }, }, } }) ``` #### Throwing Errors If you want to stop the database hook from proceeding, you can throw errors using the `APIError` class imported from `better-auth/api`. ```typescript title="auth.ts" import { betterAuth } from "better-auth"; import { APIError } from "better-auth/api"; export const auth = betterAuth({ databaseHooks: { user: { create: { before: async (user) => { if (user.isAgreedToTerms === false) { // Your special condition. // Send the API error. throw new APIError("BAD_REQUEST", { message: "User must agree to the TOS before signing up.", }); } return { data: user }; }, }, }, } }) ``` ## Plugins Schema Plugins can define their own tables in the database to store additional data. They can also add columns to the core tables to store additional data. For example, the two factor authentication plugin adds the following columns to the `user` table: * `twoFactorEnabled`: Whether two factor authentication is enabled for the user. * `twoFactorSecret`: The secret key used to generate TOTP codes. * `twoFactorBackupCodes`: Encrypted backup codes for account recovery. To add new tables and columns to your database, you have two options: `CLI`: Use the migrate or generate command. These commands will scan your database and guide you through adding any missing tables or columns. `Manual Method`: Follow the instructions in the plugin documentation to manually add tables and columns. Both methods ensure your database schema stays up-to-date with your plugins' requirements. file: ./content/docs/concepts/email.mdx meta: { "title": "Email", "description": "Learn how to use email with Better Auth." } Email is a key part of Better Auth, required for all users regardless of their authentication method. Better Auth provides email and password authentication out of the box, and a lot of utilities to help you manage email verification, password reset, and more. ## Email Verification Email verification is a security feature that ensures users provide a valid email address. It helps prevent spam and abuse by confirming that the email address belongs to the user. ### Adding Email Verification to Your App To enable email verification, you need to pass a function that sends a verification email with a link. * **sendVerificationEmail**: This function is triggered when email verification starts. It accepts a data object with the following properties: * `user`: The user object containing the email address. * `url`: The verification URL the user must click to verify their email. * `token`: The verification token used to complete the email verification to be used when implementing a custom verification URL. and a `request` object as the second parameter. ```ts title="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) => { await sendEmail({ to: user.email, subject: 'Verify your email address', text: `Click the link to verify your email: ${url}` }) } } }) ``` ### Triggering Email Verification You can initiate email verification in two ways: #### 1. During Sign-up To automatically send a verification email at signup, set `emailVerification.sendOnSignUp` to `true`. ```ts title="auth.ts" import { betterAuth } from 'better-auth'; export const auth = betterAuth({ emailVerification: { sendOnSignUp: true } }) ``` This sends a verification email when a user signs up. For social logins, email verification status is read from the SSO. With `sendOnSignUp` enabled, when the user logs in with an SSO that does not claim the email as verified, Better Auth will dispatch a verification email, but the verification is not required to login even when `requireEmailVerification` is enabled. #### 2. Require Email Verification If you enable require email verification, users must verify their email before they can log in. And every time a user tries to sign in, `sendVerificationEmail` is called. This only works if you have `sendVerificationEmail` implemented and if the user is trying to sign in with email and password. ```ts title="auth.ts" export const auth = betterAuth({ emailAndPassword: { requireEmailVerification: true } }) ``` if a user tries to sign in without verifying their email, you can handle the error and show a message to the user. ```ts title="auth-client.ts" await authClient.signIn.email({ email: "email@example.com", password: "password" }, { onError: (ctx) => { // Handle the error if(ctx.error.status === 403) { alert("Please verify your email address") } //you can also show the original error message alert(ctx.error.message) } }) ``` #### 3. Manually You can also manually trigger email verification by calling `sendVerificationEmail`. ```ts await authClient.sendVerificationEmail({ email: "user@email.com", callbackURL: "/" // The redirect URL after verification }) ``` ### Verifying the Email If the user clicks the provided verification URL, their email is automatically verified, and they are redirected to the `callbackURL`. For manual verification, you can send the user a custom link with the `token` and call the `verifyEmail` function. ```ts authClient.verifyEmail({ query: { token: "" // Pass the token here } }) ``` ### Auto SignIn After Verification To sign in the user automatically after they successfully verify their email, set the `autoSignInAfterVerification` option to `true`: ```ts const auth = betterAuth({ //...your other options emailVerification: { autoSignInAfterVerification: true } }) ``` ## Password Reset Email Password reset allows users to reset their password if they forget it. Better Auth provides a simple way to implement password reset functionality. You can enable password reset by passing a function that sends a password reset email with a link. ```ts title="auth.ts" import { betterAuth } from 'better-auth'; import { sendEmail } from './email'; // your email sending function export const auth = betterAuth({ emailAndPassword: { enabled: true, sendResetPassword: async ({ user, url, token }, request) => { await sendEmail({ to: user.email, subject: 'Reset your password', text: `Click the link to reset your password: ${url}` }) } } }) ``` Check out the [Email and Password](/docs/authentication/email-password#forget-password) guide for more details on how to implement password reset in your app. file: ./content/docs/concepts/hooks.mdx meta: { "title": "Hooks", "description": "Better Auth Hooks let you customize BetterAuth's behavior" } Hooks in Better Auth let you "hook into" the lifecycle and execute custom logic. They provide a way to customize Better Auth's behavior without writing a full plugin. We highly recommend using hooks if you need to make custom adjustments to an endpoint rather than making another endpoint outside of better auth. ## Before Hooks **Before hooks** run *before* an endpoint is executed. Use them to modify requests, pre validate data, or return early. ### Example: Enforce Email Domain Restriction This hook ensures that users can only sign up if their email ends with `@example.com`: ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { createAuthMiddleware, APIError } from "better-auth/api"; export const auth = betterAuth({ hooks: { before: createAuthMiddleware(async (ctx) => { if (ctx.path !== "/sign-up/email") { return; } if (!ctx.body?.email.endsWith("@example.com")) { throw new APIError("BAD_REQUEST", { message: "Email must end with @example.com", }); } }), }, }); ``` ### Example: Modify Request Context To adjust the request context before proceeding: ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { createAuthMiddleware } from "better-auth/api"; export const auth = betterAuth({ hooks: { before: createAuthMiddleware(async (ctx) => { if (ctx.path === "/sign-up/email") { return { context: { ...ctx, body: { ...ctx.body, name: "John Doe", }, } }; } }), }, }); ``` ## After Hooks **After hooks** run *after* an endpoint is executed. Use them to modify responses. ### Example: Send a notification to your channel when a new user is registered ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { createAuthMiddleware } from "better-auth/api"; import { sendMessage } from "@/lib/notification" export const auth = betterAuth({ hooks: { after: createAuthMiddleware(async (ctx) => { if(ctx.path.startsWith("/sign-up")){ const newSession = ctx.context.newSession; if(newSession){ sendMessage({ type: "user-register", name: newSession.user.name, }) } } }), }, }); ``` ## Ctx When you call `createAuthMiddleware` a `ctx` object is passed that provides a lot of useful properties. Including: * **Path:** `ctx.path` to get the current endpoint path. * **Body:** `ctx.body` for parsed request body (available for POST requests). * **Headers:** `ctx.headers` to access request headers. * **Request:** `ctx.request` to access the request object (may not exist in server-only endpoints). * **Query Parameters:** `ctx.query` to access query parameters. * **Context**: `ctx.context` auth related context, useful for accessing new session, auth cookies configuration, password hashing, config... and more. ### Request Response This utilities allows you to get request information and to send response from a hook. #### JSON Responses Use `ctx.json` to send JSON responses: ```ts const hook = createAuthMiddleware(async (ctx) => { return ctx.json({ message: "Hello World", }); }); ``` #### Redirects Use `ctx.redirect` to redirect users: ```ts import { createAuthMiddleware } from "better-auth/api"; const hook = createAuthMiddleware(async (ctx) => { throw ctx.redirect("/sign-up/name"); }); ``` #### Cookies * Set cookies: `ctx.setCookies` or `ctx.setSignedCookie`. * Get cookies: `ctx.getCookies` or `ctx.getSignedCookies`. Example: ```ts import { createAuthMiddleware } from "better-auth/api"; const hook = createAuthMiddleware(async (ctx) => { ctx.setCookies("my-cookie", "value"); await ctx.setSignedCookie("my-signed-cookie", "value", ctx.context.secret, { maxAge: 1000, }); const cookie = ctx.getCookies("my-cookie"); const signedCookie = await ctx.getSignedCookies("my-signed-cookie"); }); ``` #### Errors Throw errors with `APIError` for a specific status code and message: ```ts import { createAuthMiddleware, APIError } from "better-auth/api"; const hook = createAuthMiddleware(async (ctx) => { throw new APIError("BAD_REQUEST", { message: "Invalid request", }); }); ``` ### Context The `ctx` object contains another `context` object inside that's meant to hold contexts related to auth. Including a newly created session on after hook, cookies configuration, password hasher and so on. #### New Session The newly created session after an endpoint is run. This only exist in after hook. ```ts title="auth.ts" createAuthMiddleware(async (ctx) => { const newSession = ctx.context.newSession }); ``` #### Predefined Auth Cookies Access BetterAuth’s predefined cookie properties: ```ts title="auth.ts" createAuthMiddleware(async (ctx) => { const cookieName = ctx.context.authCookies.sessionToken.name; }); ``` #### Secret You can access the `secret` for your auth instance on `ctx.context.secret` #### Password The password object provider `hash` and `verify` * `ctx.context.password.hash`: let's you hash a given password. * `ctx.context.password.verify`: let's you verify given `password` and a `hash`. #### Adapter Adapter exposes the adapter methods used by better auth. Including `findOne`, `findMany`, `create`, `delete`, `update` and `updateMany`. You generally should use your actually `db` instance from your orm rather than this adapter. #### Internal Adapter These are calls to your db that perform specific actions. `createUser`, `createSession`, `updateSession`... This may be useful to use instead of using your db directly to get access to `databaseHooks`, proper `secondaryStorage` support and so on. If you're make a query similar to what exist in this internal adapter actions it's worth a look. #### generateId You can use `ctx.context.generateId` to generate Id for various reasons. ## Reusable Hooks If you need to reuse a hook across multiple endpoints, consider creating a plugin. Learn more in the [Plugins Documentation](/docs/concepts/plugins). file: ./content/docs/concepts/oauth.mdx meta: { "title": "OAuth", "description": "How Better Auth handles OAuth" } Better Auth comes with built-in support for OAuth 2.0 and OpenID Connect. This allows you to authenticate users via popular OAuth providers like Google, Facebook, GitHub, and more. If your desired provider isn’t directly supported, you can use the [Generic OAuth Plugin](/docs/plugins/generic-oauth) for custom integrations. ## Configuring Social Providers To enable a social provider, you need to provide `clientId` and `clientSecret` for the provider. Here’s an example of how to configure Google as a provider: ```ts title="auth.ts" import { betterAuth } from 'better-auth'; export const auth = betterAuth({ // Other configurations... socialProviders: { google: { clientId: 'YOUR_GOOGLE_CLIENT_ID', clientSecret: 'YOUR_GOOGLE_CLIENT_SECRET', } } }) ``` ### Other Provider Configurations **scope** The scope of the access request. For example, `email` or `profile`. **redirectURI** Custom redirect URI for the provider. By default, it uses `/api/auth/callback/${providerName}`. **disableIdTokenSignIn:** Disables the use of the ID token for sign-in. By default, it’s enabled for some providers like Google and Apple. **verifyIdToken** A custom function to verify the ID token. **getUserInfo** A custom function to fetch user information from the provider. Given the tokens returned from the provider, this function should return the user’s information. **mapProfileToUser** A custom function to map the user profile returned from the provider to the user object in your database. Useful, if you have additional fields in your user object you want to populate from the provider’s profile. Or if you want to change how by default the user object is mapped. ```ts title="auth.ts" import { betterAuth } from 'better-auth'; export const auth = betterAuth({ // Other configurations... socialProviders: { google: { clientId: 'YOUR_GOOGLE_CLIENT_ID', clientSecret: 'YOUR_GOOGLE_CLIENT_SECRET', mapProfileToUser: (profile) => { return { firstName: profile.given_name, lastName: profile.family_name, } } } } }) ``` ## How OAuth Works in Better Auth Here’s what happens when a user selects a provider to authenticate with: 1. **Configuration Check:** Ensure the necessary provider details (e.g., client ID, secret) are configured. 2. **State Generation:** Generate and save a state token in your database for CSRF protection. 3. **PKCE Support:** If applicable, create a PKCE code challenge and verifier for secure exchanges. 4. **Authorization URL Construction:** Build the provider’s authorization URL with parameters like client ID, redirect URI, state, etc. The callback URL usually follows the pattern `/api/auth/callback/${providerName}`. 5. **User Redirection:** * If redirection is enabled, users are redirected to the provider’s login page. * If redirection is disabled, the authorization URL is returned for the client to handle the redirection. ### Post-Login Flow After the user completes the login process, the provider redirects them back to the callback URL with a code and state. Better Auth handles the rest: 1. **Token Exchange:** The code is exchanged for an access token and user information. 2. **User Handling:** * If the user doesn’t exist, a new account is created. * If the user exists, they are logged in. * If the user has multiple accounts across providers, Better Auth links them based on your configuration. Learn more about [account linking](/docs/concepts/users-accounts#account-linking). 3. **Session Creation:** A new session is created for the user. 4. **Redirect:** Users are redirected to the specified URL provided during the initial request or `/`. If any error occurs during the process, Better Auth handles it and redirects the user to the error URL (if provided) or the callbackURL. And it includes the error message in the query string `?error=...`. file: ./content/docs/concepts/plugins.mdx meta: { "title": "Plugins", "description": "Learn how to use plugins with Better Auth." } Plugins are a key part of Better Auth, they let you extend the base functionalities. You can use them to add new authentication methods, features, or customize behaviors. Better Auth offers comes with many built-in plugins ready to use. Check the plugins section for details. You can also create your own plugins. ## Using a Plugin Plugins can be a server-side plugin, a client-side plugin, or both. To add a plugin on the server, include it in the `plugins` array in your auth configuration. The plugin will initialize with the provided options. ```ts title="server.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ plugins: [ // Add your plugins here ] }); ``` Client plugins are added when creating the client. Most plugin require both server and client plugins to work correctly. The Better Auth auth client on the frontend uses the `createAuthClient` function provided by `better-auth/client`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client"; const authClient = createAuthClient({ plugins: [ // Add your client plugins here ] }); ``` We recommend keeping the auth-client and your normal auth instance in separate files. ## Creating a Plugin To get started, you'll need a server plugin. Server plugins are the backbone of all plugins, and client plugins are there to provide an interface with frontend APIs to easily work with your server plugins. If your server plugins has endpoints that needs to be called from the client, you'll also need to create a client plugin. ### What can a plugin do? * Create custom `endpoint`s to perform any action you want. * Extend database tables with custom `schemas`. * Use a `middleware` to target a group of routes using it's route matcher, and run only when those routes are called through a request. * Use `hooks` to target a specific route or request. And if you want to run the hook even if the endpoint is called directly. * Use `onRequest` or `onResponse` if you want to do something that affects all requests or responses. * Create custom `rate-limit` rule. ## Create a Server plugin To create a server plugin you need to pass an object that satisfies the `BetterAuthPlugin` interface. The only required property is `id`, which is a unique identifier for the plugin. Both server and client plugins can use the same `id`. ```ts title="plugin.ts" import type { BetterAuthPlugin } from "better-auth"; export const myPlugin = ()=>{ return { id: "my-plugin", } satisfies BetterAuthPlugin } ``` You don't have to make the plugin a function, but it's recommended to do so. This way you can pass options to the plugin and it's consistent with the built-in plugins. ### Endpoints To add endpoints to the server, you can pass `endpoints` which requires an object with the key being any `string` and the value being an `AuthEndpoint`. To create an Auth Endpoint you'll need to import `createAuthEndpoint` from `better-auth`. Better Auth uses wraps around another library called Better Call to create endpoints. Better call is a simple ts web framework made by the same team behind Better Auth. ```ts title="plugin.ts" import { createAuthEndpoint } from "better-auth/api"; const myPlugin = ()=> { return { id: "my-plugin", endpoints: { getHelloWorld: createAuthEndpoint("/my-plugin/hello-world", { method: "GET", }, async(ctx) => { return ctx.json({ message: "Hello World" }) }) } } satisfies BetterAuthPlugin } ``` Create Auth endpoints wraps around `createEndpoint` from Better Call. Inside the `ctx` object, it'll provide another object called `context` that give you access better-auth specific contexts including `options`, `db`, `baseURL` and more. **Context Object** * `appName`: The name of the application. Defaults to "Better Auth". * `options`: The options passed to the Better Auth instance. * `tables`: Core tables definition. It is an object which has the table name as the key and the schema definition as the value. * `baseURL`: the baseURL of the auth server. This includes the path. For example, if the server is running on `http://localhost:3000`, the baseURL will be `http://localhost:3000/api/auth` by default unless changed by the user. * `session`: The session configuration. Includes `updateAge` and `expiresIn` values. * `secret`: The secret key used for various purposes. This is defined by the user. * `authCookie`: The default cookie configuration for core auth cookies. * `logger`: The logger instance used by Better Auth. * `db`: The Kysely instance used by Better Auth to interact with the database. * `adapter`: This is the same as db but it give you `orm` like functions to interact with the database. (we recommend using this over `db` unless you need raw sql queries or for performance reasons) * `internalAdapter`: These are internal db calls that are used by Better Auth. For example, you can use these calls to create a session instead of using `adapter` directly. `internalAdapter.createSession(userId)` * `createAuthCookie`: This is a helper function that let's you get a cookie `name` and `options` for either to `set` or `get` cookies. It implements things like `__secure` prefix and `__host` prefix for cookies based on For other properties, you can check the Better Call documentation and the source code . **Rules for Endpoints** * Makes sure you use kebab-case for the endpoint path * Make sure to only use `POST` or `GET` methods for the endpoints. * Any function that modifies a data should be a `POST` method. * Any function that fetches data should be a `GET` method. * Make sure to use the `createAuthEndpoint` function to create API endpoints. * Make sure your paths are unique to avoid conflicts with other plugins. If you're using a common path, add the plugin name as a prefix to the path. (`/my-plugin/hello-world` instead of `/hello-world`.) ### Schema You can define a database schema for your plugin by passing a `schema` object. The schema object should have the table name as the key and the schema definition as the value. ```ts title="plugin.ts" import { BetterAuthPlugin } from "better-auth/plugins"; const myPlugin = ()=> { return { id: "my-plugin", schema: { myTable: { fields: { name: { type: "string" } }, modelName: "myTable" // optional if you want to use a different name than the key } } } satisfies BetterAuthPlugin } ``` **Fields** By default better-auth will create an `id` field for each table. You can add additional fields to the table by adding them to the `fields` object. The key is the column name and the value is the column definition. The column definition can have the following properties: `type`: The type of the filed. It can be `string`, `number`, `boolean`, `date`. `required`: if the field should be required on a new record. (default: `false`) `unique`: if the field should be unique. (default: `false`) `reference`: if the field is a reference to another table. (default: `null`) It takes an object with the following properties: * `model`: The table name to reference. * `field`: The field name to reference. * `onDelete`: The action to take when the referenced record is deleted. (default: `null`) **Other Schema Properties** `disableMigration`: if the table should not be migrated. (default: `false`) ```ts title="plugin.ts" const myPlugin = (opts: PluginOptions)=>{ return { id: "my-plugin", schema: { rateLimit: { fields: { key: { type: "string", }, }, disableMigration: opts.storage.provider !== "database", // [!code highlight] }, }, } satisfies BetterAuthPlugin } ``` if you add additional fields to a `user` or `session` table, the types will be inferred automatically on `getSession` and `signUpEmail` calls. ```ts title="plugin.ts" const myPlugin = ()=>{ return { id: "my-plugin", schema: { user: { fields: { age: { type: "number", }, }, }, }, } satisfies BetterAuthPlugin } ``` This will add an `age` field to the `user` table and all `user` returning endpoints will include the `age` field and it'll be inferred properly by typescript. Don't store sensitive information in `user` or `session` table. Crate a new table if you need to store sensitive information. ### Hooks Hooks are used to run code before or after an action is performed, either from a client or directly on the server. You can add hooks to the server by passing a `hooks` object, which should contain `before` and `after` properties. ```ts title="plugin.ts" import { createAuthMiddleware } from "better-auth/plugins"; const myPlugin = ()=>{ return { id: "my-plugin", hooks: { before: [{ matcher: (context)=>{ return context.headers.get("x-my-header") === "my-value" }, handler: createAuthMiddleware(async(ctx)=>{ //do something before the request return { context: ctx // if you want to modify the context } }) }], after: [{ matcher: (context)=>{ return context.path === "/sign-up/email" }, handler: async(ctx)=>{ return ctx.json({ message: "Hello World" }) // if you want to modify the response } }] } } satisfies BetterAuthPlugin } ``` ### Middleware You can add middleware to the server by passing a `middleware` array. This array should contain middleware objects, each with a `path` and a `middleware` property. Unlike hooks, middleware only runs on `api` requests from a client. If the endpoint is invoked directly, the middleware will not run. The `path` can be either a string or a path matcher, using the same path-matching system as `better-call`. If you throw an `APIError` from the middleware or returned a `Response` object, the request will be stopped and the response will be sent to the client. ```ts title="plugin.ts" const myPlugin = ()=>{ return { id: "my-plugin", middleware: [ { path: "/my-plugin/hello-world", middleware: createAuthMiddleware(async(ctx)=>{ //do something }) } ] } satisfies BetterAuthPlugin } ``` ### On Request & On Response Additional to middlewares, you can also hook into right before a request is made and right after a response is returned. This is mostly useful if you want to do something that affects all requests or responses. #### On Request The `onRequest` function is called right before the request is made. It takes two parameters: the `request` and the `context` object. Here’s how it works: * **Continue as Normal**: If you don't return anything, the request will proceed as usual. * **Interrupt the Request**: To stop the request and send a response, return an object with a `response` property that contains a `Response` object. * **Modify the Request**: You can also return a modified `request` object to change the request before it's sent. ```ts title="plugin.ts" const myPlugin = ()=> { return { id: "my-plugin", onRequest: async (request, context) => { //do something }, } satisfies BetterAuthPlugin } ``` #### On Response The `onResponse` function is executed immediately after a response is returned. It takes two parameters: the `response` and the `context` object. Here’s how to use it: * **Modify the Response**: You can return a modified response object to change the response before it is sent to the client. * **Continue Normally**: If you don’t return anything, the response will be sent as is. ```ts title="plugin.ts" const myPlugin = ()=>{ return { id: "my-plugin", onResponse: async (response, context) => { //do something }, } satisfies BetterAuthPlugin } ``` ### Rate Limit You can define custom rate limit rules for your plugin by passing a `rateLimit` array. The rate limit array should contain an array of rate limit objects. ```ts title="plugin.ts" const myPlugin = ()=>{ return { id: "my-plugin", rateLimit: [ { pathMatcher: (path)=>{ return path === "/my-plugin/hello-world" }, limit: 10, window: 60, } ] } satisfies BetterAuthPlugin } ``` ### Server-plugin helper functions Some additional helper functions for creating server plugins. #### `getSessionFromCtx` Allows you to get the client's session data by passing the auth middleware's `context`. ```ts title="plugin.ts" import { createAuthMiddleware } from "better-auth/plugins"; const myPlugin = { id: "my-plugin", hooks: { before: [{ matcher: (context)=>{ return context.headers.get("x-my-header") === "my-value" }, handler: createAuthMiddleware(async (ctx) => { const session = await getSessionFromCtx(ctx); //do something with the client's session. return { context: ctx } }) }], } } satisfies BetterAuthPlugin ``` #### `sessionMiddleware` A middleware that checks if the client has a valid session. If the client has a valid session, it'll add the session data to the context object. ```ts title="plugin.ts" import { createAuthMiddleware } from "better-auth/plugins"; import { sessionMiddleware } from "better-auth/api"; const myPlugin = ()=>{ return { id: "my-plugin", endpoints: { getHelloWorld: createAuthEndpoint("/my-plugin/hello-world", { method: "GET", use: [sessionMiddleware], // [!code highlight] }, async(ctx) => { const session = ctx.context.session; return ctx.json({ message: "Hello World" }) }) } } satisfies BetterAuthPlugin } ``` ## Creating a client plugin If your endpoints needs to be called from the client, you'll need to also create a client plugin. Better Auth clients can infer the endpoints from the server plugins. You can also add additional client side logic. ```ts title="client-plugin.ts" import type { BetterAuthClientPlugin } from "better-auth"; export const myPluginClient = ()=>{ return { id: "my-plugin", } satisfies BetterAuthClientPlugin } ``` ### Endpoint Interface Endpoints are inferred from the server plugin by adding a `$InferServerPlugin` key to the client plugin. The client infers the `path` as an object and converts kebab-case to camelCase. For example, `/my-plugin/hello-world` becomes `myPlugin.helloWorld`. ```ts title="client-plugin.ts" import type { BetterAuthClientPlugin } from "better-auth/client"; import type { myPlugin } from "./plugin"; const myPluginClient = ()=> { return { id: "my-plugin", $InferServerPlugin: {} as ReturnType, } satisfies BetterAuthClientPlugin } ``` ### Get actions If you need to add additional methods or what not to the client you can use the `getActions` function. This function is called with the `fetch` function from the client. Better Auth uses Better fetch to make requests. Better fetch is a simple fetch wrapper made by the same author of Better Auth. ```ts title="client-plugin.ts" import type { BetterAuthClientPlugin } from "better-auth/client"; import type { myPlugin } from "./plugin"; import type { BetterFetchOption } from "@better-fetch/fetch"; const myPluginClient = { id: "my-plugin", $InferServerPlugin: {} as ReturnType, getActions: ($fetch)=>{ return { myCustomAction: async (data: { foo: string, }, fetchOptions?: BetterFetchOption)=>{ const res = $fetch("/custom/action", { method: "POST", body: { foo: data.foo }, ...fetchOptions }) return res } } } } satisfies BetterAuthClientPlugin ``` As a general guideline, ensure that each function accepts only one argument, with an optional second argument for fetchOptions to allow users to pass additional options to the fetch call. The function should return an object containing data and error keys. If your use case involves actions beyond API calls, feel free to deviate from this rule. ### Get Atoms This is only useful if you want to provide `hooks` like `useSession`. Get atoms is called with the `fetch` function from better fetch and it should return an object with the atoms. The atoms should be created using nanostores. The atoms will be resolved by each framework `useStore` hook provided by nanostores. ```ts title="client-plugin.ts" import { atom } from "nanostores"; import type { BetterAuthClientPlugin } from "better-auth/client"; const myPluginClient = { id: "my-plugin", $InferServerPlugin: {} as ReturnType, getAtoms: ($fetch)=>{ const myAtom = atom() return { myAtom } } } satisfies BetterAuthClientPlugin ``` See built in plugins for examples of how to use atoms properly. ### Path methods by default, inferred paths use `GET` method if they don't require a body and `POST` if they do. You can override this by passing a `pathMethods` object. The key should be the path and the value should be the method ("POST" | "GET"). ```ts title="client-plugin.ts" import type { BetterAuthClientPlugin } from "better-auth/client"; import type { myPlugin } from "./plugin"; const myPluginClient = { id: "my-plugin", $InferServerPlugin: {} as ReturnType, pathMethods: { "/my-plugin/hello-world": "POST" } } satisfies BetterAuthClientPlugin ``` ### Fetch plugins If you need to use better fetch plugins you can pass them to the `fetchPlugins` array. You can read more about better fetch plugins in the better fetch documentation. ### Atom Listeners This is only useful if you want to provide `hooks` like `useSession` and you want to listen to atoms and re-evaluate them when they change. You can see how this is used in the built-in plugins. file: ./content/docs/concepts/rate-limit.mdx meta: { "title": "Rate Limit", "description": "How to limit the number of requests a user can make to the server in a given time period." } Better Auth includes a built-in rate limiter to help manage traffic and prevent abuse. By default, in production mode, the rate limiter is set to: * Window: 60 seconds * Max Requests: 100 requests You can easily customize these settings by passing the rateLimit object to the betterAuth function. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ rateLimit: { window: 10, // time window in seconds max: 100, // max requests in the window }, }) ``` In addition to the default settings, Better Auth provides custom rules for specific paths. For example: * `/sign-in/email`: Is limited to 3 requests within 10 seconds. In addition, plugins also define custom rules for specific paths. For example, `twoFactor` plugin has custom rules: * `/two-factor/verify`: Is limited to 3 requests within 10 seconds. These custom rules ensure that sensitive operations are protected with stricter limits. ## Configuring Rate Limit ### Rate Limit Window ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ //...other options rateLimit: { window: 60, // time window in seconds max: 100, // max requests in the window }, }) ``` You can also pass custom rules for specific paths. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ //...other options rateLimit: { window: 60, // time window in seconds max: 100, // max requests in the window customRules: { "/sign-in/email": { window: 10, max: 3, }, "/two-factor/*": async (request)=> { // custom function to return rate limit window and max return { window: 10, max: 3, } } }, }, }) ``` ### Storage By default, rate limit data is stored in memory, which may not be suitable for many use cases, particularly in serverless environments. To address this, you can use a database, secondary storage, or custom storage for storing rate limit data. **Using Database** ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ //...other options rateLimit: { storage: "database", modelName: "rateLimit", //optional by default "rateLimit" is used }, }) ``` Make sure to run `migrate` to create the rate limit table in your database. ```bash npx @better-auth/cli migrate ``` **Using Secondary Storage** If a [Secondary Storage](/docs/concepts/database#secondary-storage) has been configured you can use that to store rate limit data. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ //...other options rateLimit: { storage: "secondary-storage" }, }) ``` **Custom Storage** If none of the above solutions suits your use case you can implement a `customStorage`. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ //...other options rateLimit: { customStorage: { get: async (key) => { // get rate limit data }, set: async (key, value) => { // set rate limit data }, }, }, }) ``` ## Handling Rate Limit Errors When a request exceeds the rate limit, Better Auth returns the following header: * `X-Retry-After`: The number of seconds until the user can make another request. To handle rate limit errors on the client side, you can manage them either globally or on a per-request basis. Since Better Auth clients wrap over Better Fetch, you can pass `fetchOptions` to handle rate limit errors **Global Handling** ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client"; export const authClient = createAuthClient({ fetchOptions: { onError: async (context) => { const { response } = context; if (response.status === 429) { const retryAfter = response.headers.get("X-Retry-After"); console.log(`Rate limit exceeded. Retry after ${retryAfter} seconds`); } }, } }) ``` **Per Request Handling** ```ts title="auth-client.ts" import { client } from "./client"; await authClient.signIn.email({ fetchOptions: { onError: async (context) => { const { response } = context; if (response.status === 429) { const retryAfter = response.headers.get("X-Retry-After"); console.log(`Rate limit exceeded. Retry after ${retryAfter} seconds`); } }, } }) ``` ### Schema If you are using a database to store rate limit data you need this schema: Table Name: `rateLimit` file: ./content/docs/concepts/session-management.mdx meta: { "title": "Session Management", "description": "Better Auth session management." } Better Auth manages session using a traditional cookie-based session management. The session is stored in a cookie and is sent to the server on every request. The server then verifies the session and returns the user data if the session is valid. ## Session table The session table stores the session data. The session table has the following fields: * `id`: The session token. Which is also used as the session cookie. * `userId`: The user id of the user. * `expiresAt`: The expiration date of the session. * `ipAddress`: The IP address of the user. * `userAgent`: The user agent of the user. It stores the user agent header from the request. ## Session Expiration The session expires after 7 days by default. But whenever the session is used, and the `updateAge` is reached the session expiration is updated to the current time plus the `expiresIn` value. You can change both the `expiresIn` and `updateAge` values by passing the `session` object to the `auth` configuration. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ //... other config options session: { expiresIn: 60 * 60 * 24 * 7, // 7 days updateAge: 60 * 60 * 24 // 1 day (every 1 day the session expiration is updated) } }) ``` ## Session Freshness Some endpoints in Better Auth require the session to be **fresh**. A session is considered fresh if its `createdAt` is within the `freshAge` limit. By default, the `freshAge` is set to **1 day** (60 \* 60 \* 24). You can customize the `freshAge` value by passing a `session` object in the `auth` configuration: ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ //... other config options session: { freshAge: 60 * 5 // 5 minutes (the session is fresh if created within the last 5 minutes) } }) ``` To **disable the freshness check**, set `freshAge` to `0`: ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ //... other config options session: { freshAge: 0 // Disable freshness check } }) ``` ## Session Management Better Auth provides a set of functions to manage sessions. ### Get Session The `getSession` function retrieves the current active session. ```ts client="client.ts" import { authClient } from "@/lib/client" const { data: session } = await authClient.getSession() ``` To learn how to customize the session response check the [Customizing Session Response](#customizing-session-response) section. ### Use Session The `useSession` action provides a reactive way to access the current session. ```ts client="client.ts" import { authClient } from "@/lib/client" const { data: session } = authClient.useSession() ``` ### List Sessions The `listSessions` function returns a list of sessions that are active for the user. ```ts title="auth-client.ts" import { authClient } from "@/lib/client" const sessions = await authClient.listSessions() ``` ### Revoke Session When a user signs out of a device, the session is automatically ended. However, you can also end a session manually from any device the user is signed into. To end a session, use the `revokeSession` function. Just pass the session token as a parameter. ```ts title="auth-client.ts" await authClient.revokeSession({ token: "session-token" }) ``` ### Revoke Other Sessions To revoke all other sessions except the current session, you can use the `revokeOtherSessions` function. ```ts title="auth-client.ts" await authClient.revokeOtherSessions() ``` ### Revoke All Sessions To revoke all sessions, you can use the `revokeSessions` function. ```ts title="auth-client.ts" await authClient.revokeSessions() ``` ### Revoking Sessions on Password Change You can revoke all sessions when the user changes their password by passing `revokeOtherSessions` as true on `changePassword` function. ```ts title="auth.ts" await authClient.changePassword({ newPassword: newPassword, currentPassword: currentPassword, revokeOtherSessions: true, }) ``` ## Session Caching ### Cookie Cache Calling your database every time `useSession` or `getSession` invoked isn’t ideal, especially if sessions don’t change frequently. Cookie caching handles this by storing session data in a short-lived, signed cookie—similar to how JWT access tokens are used with refresh tokens. When cookie caching is enabled, the server can check session validity from the cookie itself instead of hitting the database each time. The cookie is signed to prevent tampering, and a short `maxAge` ensures that the session data gets refreshed regularly. If a session is revoked or expires, the cookie will be invalidated automatically. To turn on cookie caching, just set `session.cookieCache` in your auth config: ```ts title="auth.ts" const auth = new BetterAuth({ session: { cookieCache: { enabled: true, maxAge: 5 * 60 // Cache duration in seconds } } }); ``` If you want to disable returning from the cookie cache when fetching the session, you can pass `disableCookieCache:true` ```ts title="auth-client.ts" const session = await authClient.getSession({ query: { disableCookieCache: true }}) ``` or on the server ```ts title="server.ts" auth.api.getSession({ query: { disableCookieCache: true, }, headers: req.headers, // pass the headers }); ``` ## Customizing Session Response When you call `getSession` or `useSession`, the session data is returned as a `user` and `session` object. You can customize this response using the `customSession` plugin. ```ts title="auth.ts" import { customSession } from "better-auth/plugins"; export const auth = betterAuth({ plugins: [ customSession(async ({ user, session }) => { const roles = findUserRoles(session.session.userId); return { roles, user: { ...user, newField: "newField", }, session }; }), ], }); ``` This will add `roles` and `user.newField` to the session response. **Infer on the Client** ```ts title="auth-client.ts" import { customSessionClient } from "better-auth/client/plugins"; import type { auth } from "@/lib/auth"; // Import the auth instance as a type const authClient = createAuthClient({ plugins: [customSessionClient()], }); const { data } = await authClient.useSession(); const { data: sessionData } = await authClient.getSession(); // data.roles // data.user.newField ``` **Some Caveats**: * The passed `session` object to the callback does not infer fields added by plugins. However, as a workaround, you can pull up your auth options and pass it to the plugin to infer the fields. ```ts import { betterAuth, BetterAuthOptions } from "better-auth"; const options = { //...config options plugins: [ //...plugins ] } satisfies BetterAuthOptions; export const auth = betterAuth({ ...options, plugins: [ ...(options.plugins ?? []), customSession(async ({ user, session }) => { // now both user and session will infer the fields added by plugins and your custom fields return { user, session } }, options), // pass options here // [!code highlight] ] }) ``` * If you cannot use the `auth` instance as a type, inference will not work on the client. * Session caching, including secondary storage or cookie cache, does not include custom fields. Each time the session is fetched, your custom session function will be called. file: ./content/docs/concepts/typescript.mdx meta: { "title": "TypeScript", "description": "Better Auth TypeScript integration." } Better Auth is designed to be type-safe. Both the client and server are built with TypeScript, allowing you to easily infer types. ## Typescript Config ### Strict Mode Better Auth is designed to work with TypeScript's strict mode. We recommend enabling strict mode in your TypeScript config file: ```json title="tsconfig.json" { "compilerOptions": { "strict": true } } ``` if you can't set `strict` to `true`, you can enable `strictNullChecks`: ```json title="tsconfig.json" { "compilerOptions": { "strictNullChecks": true, } } ``` ## Inferring Types Both the client SDK and the server offer types that can be inferred using the `$Infer` property. Plugins can extend base types like `User` and `Session`, and you can use `$Infer` to infer these types. Additionally, plugins can provide extra types that can also be inferred through `$Infer`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() export type Session = typeof authClient.$Infer.Session ``` The `Session` type includes both `session` and `user` properties. The user property represents the user object type, and the `session` property represents the `session` object type. You can also infer types on the server side. ```ts title="auth.ts" import { betterAuth } from "better-auth" import Database from "better-sqlite3" export const auth = betterAuth({ database: new Database("database.db") }) type Session = typeof auth.$Infer.Session ``` ## Additional Fields Better Auth allows you to add additional fields to the user and session objects. All additional fields are properly inferred and available on the server and client side. ```ts import { betterAuth } from "better-auth" import Database from "better-sqlite3" export const auth = betterAuth({ database: new Database("database.db"), user: { additionalFields: { role: { type: "string" } } } }) type Session = typeof auth.$Infer.Session ``` In the example above, we added a `role` field to the user object. This field is now available on the `Session` type. ### Inferring Additional Fields on Client To make sure proper type inference for additional fields on the client side, you need to inform the client about these fields. There are two approaches to achieve this, depending on your project structure: 1. For Monorepo or Single-Project Setups If your server and client code reside in the same project, you can use the `inferAdditionalFields` plugin to automatically infer the additional fields from your server configuration. ```ts import { inferAdditionalFields } from "better-auth/client/plugins"; import { createAuthClient } from "better-auth/react"; import type { auth } from "./auth"; export const authClient = createAuthClient({ plugins: [inferAdditionalFields()], }); ``` 2. For Separate Client-Server Projects If your client and server are in separate projects, you'll need to manually specify the additional fields when creating the auth client. ```ts import type { auth } from "./auth"; import { inferAdditionalFields } from "better-auth/client/plugins"; export const authClient = createAuthClient({ plugins: [inferAdditionalFields({ user: { role: { type: "string" } } })], }); ``` file: ./content/docs/concepts/users-accounts.mdx meta: { "title": "User & Accounts", "description": "User and account management." } Beyond authenticating users, Better Auth also provides a set of methods to manage users. This includes, updating user information, changing passwords, and more. ## User table The user table stores the user data. The user table has the following fields: * `id`: The user id. * `email`: The email of the user. * `name`: The name of the user. * `image`: The image of the user. * `createdAt`: The creation date of the user. * `updatedAt`: The last update date of the user. The user table can be extended using [additional fields](/docs/concepts/database#extending-core-schema) or by plugins to store additional data. ## Update User ### Update User Information To update user information, you can use the `updateUser` function provided by the client. The `updateUser` function takes an object with the following properties: ```ts await authClient.updateUser({ image: "https://example.com/image.jpg", name: "John Doe", }) ``` ### Change Email To allow users to change their email, first enable the `changeEmail` feature, which is disabled by default. Set `changeEmail.enabled` to `true`: ```ts export const auth = betterAuth({ user: { changeEmail: { enabled: true, } } }) ``` For users with a verified email, provide the `sendChangeEmailVerification` function. This function triggers when a user changes their email, sending a verification email with a URL and token. If the current email isn't verified, the change happens immediately without verification. ```ts export const auth = betterAuth({ user: { changeEmail: { enabled: true, sendChangeEmailVerification: async ({ user, newEmail, url, token }, request) => { await sendEmail({ to: user.email, // verification email must be sent to the current user email to approve the change subject: 'Approve email change', text: `Click the link to approve the change: ${url}` }) } } } }) ``` Once enabled, use the `changeEmail` function on the client to update a user’s email. The user must verify their current email before changing it. ```ts await authClient.changeEmail({ newEmail: "new-email@email.com", callbackURL: "/dashboard", //to redirect after verification }); ``` After verification, the new email is updated in the user table, and a confirmation is sent to the new address. If the current email is unverified, the new email is updated without the verification step. ### Change Password Password of a user isn't stored in the user table. Instead, it's stored in the account table. To change the password of a user, you can use the `changePassword` function provided by the client. The `changePassword` function takes an object with the following properties: ```ts await authClient.changePassword({ newPassword: "newPassword123", currentPassword: "oldPassword123", revokeOtherSessions: true, // revoke all other sessions the user is signed into }); ``` ### Set Password If a user was registered using OAuth or other providers, they won't have a password or a credential account. In this case, you can use the `setPassword` action to set a password for the user. For security reasons, this function can only be called from the server. We recommend having users go through a 'forgot password' flow to set a password for their account. ```ts await auth.api.setPassword({ body: { newPassword: "password" }, headers: // }); ``` ## Delete User Better Auth provides a utility to hard delete a user from your database. It's disabled by default, but you can enable it easily by passing `enabled:true` ```ts export const auth = betterAuth({ //...other config user: { deleteUser: { // [!code highlight] enabled: true // [!code highlight] } // [!code highlight] } }) ``` Once enabled, you can call `authClient.deleteUser` to permanently delete user data from your database. ### Adding Verification Before Deletion For added security, you’ll likely want to confirm the user’s intent before deleting their account. A common approach is to send a verification email. Better Auth provides a `sendDeleteAccountVerification` utility for this purpose. Here’s how you can set it up: ```ts export const auth = betterAuth({ user: { deleteUser: { enabled: true, sendDeleteAccountVerification: async ( { user, // The user object url, // The auto-generated URL for deletion token // The verification token (can be used to generate custom URL) }, request // The original request object (optional) ) => { // Your email sending logic here // Example: sendEmail(data.user.email, "Verify Deletion", data.url); }, }, }, }); ``` **How callback verification works:** * **Callback URL**: The url provided in `sendDeleteAccountVerification` is a pre-generated link that deletes the user data when accessed. ```ts title="delete-user.ts" await authClient.deleteUser({ callbackURL: "/goodbye" // you can provide a callback URL to redirect after deletion }); ``` * **Authentication Check**: The user must be signed in to the account they’re attempting to delete. If they aren’t signed in, the deletion process will fail. If you have sent a custom URL, you can use the `deleteUser` method with the token to delete the user. ```ts title="delete-user.ts" await authClient.deleteUser({ token }); ``` ### Authentication Requirements To delete a user, the user must meet one of the following requirements: 1. A valid password if the user has a password, they can delete their account by providing the password. ```ts title="delete-user.ts" await authClient.deleteUser({ password: "password" }); ``` 2. Fresh session The user must have a `fresh` session token, meaning the user must have signed in recently. This is checked if the password is not provided. By default `session.freshAge` is set to `60 * 60 * 24` (1 day). You can change this value by passing the `session` object to the `auth` configuration. If it is set to `0`, the freshness check is disabled. ```ts title="delete-user.ts" await authClient.deleteUser(); ``` 3. The user must provide a token generated by the `sendDeleteAccountVerification` callback. ```ts title="delete-user.ts" await authClient.deleteUser({ token }); ``` ### Callbacks **beforeDelete**: This callback is called before the user is deleted. You can use this callback to perform any cleanup or additional checks before deleting the user. ```ts title="auth.ts" export const auth = betterAuth({ user: { deleteUser: { enabled: true, beforeDelete: async (user) => { // Perform any cleanup or additional checks here }, }, }, }); ``` you can also throw `APIError` to interrupt the deletion process. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { APIError } from "better-auth/api"; export const auth = betterAuth({ user: { deleteUser: { enabled: true, beforeDelete: async (user, request) => { if (user.email.includes("admin")) { throw new APIError("BAD_REQUEST", { message: "Admin accounts can't be deleted", }); } }, }, }, }); ``` **afterDelete**: This callback is called after the user is deleted. You can use this callback to perform any cleanup or additional actions after the user is deleted. ```ts title="auth.ts" export const auth = betterAuth({ user: { deleteUser: { enabled: true, afterDelete: async (user, request) => { // Perform any cleanup or additional actions here }, }, }, }); ``` ## Accounts Better Auth supports multiple authentication methods. Each authentication method is called a provider. For example, email and password authentication is a provider, Google authentication is a provider, etc. When a user signs in using a provider, an account is created for the user. The account stores the authentication data returned by the provider. This data includes the access token, refresh token, and other information returned by the provider. ### Account table The account table stores the authentication data of the user. The account table has the following fields: * `id`: The unique identifier of the account. * `userId`: The id of the user. * `accountId`: The id of the account provided by the OAuth provider. (optional) * `providerId`: The id of the provider. (optional) * `accessToken`: The access token of the account. Returned by the provider. (optional) * `refreshToken`: The refresh token of the account. Returned by the provider. (optional) * `expiresAt`: The time when the access token expires. (optional) * `password`: The password of the account. Mainly used for email and password authentication. (optional) ### List User Accounts To list user accounts you can use `client.user.listAccounts` method. Which will return all accounts associated with a user. ```ts const accounts = await authClient.listAccounts(); ``` ### Account Linking Account linking enables users to associate multiple authentication methods with a single account. With Better Auth, users can connect additional social sign-ons or OAuth providers to their existing accounts if the provider confirms the user's email as verified. If account linking is disabled, no accounts can be linked, regardless of the provider or email verification status. ```ts title="auth.ts" const auth = new BetterAuth({ account: { accountLinking: { enabled: true, } }, }); ``` #### Forced Linking You can specify a list of "trusted providers." When a user logs in using a trusted provider, their account will be automatically linked even if the provider doesn’t confirm the email verification status. Use this with caution as it may increase the risk of account takeover. ```ts title="auth.ts" const auth = new BetterAuth({ account: { accountLinking: { enabled: true, trustedProviders: ["google", "github"] } }, }); ``` #### Manually Linking Accounts Users already signed in can manually link their account to additional social providers or credential-based accounts. * **Linking Social Accounts:** Use the `user.linkSocial` method on the client to link a social provider to the user's account. ```ts await authClient.linkSocial({ provider: "google", // Provider to link callbackURL: "/callback" // Callback URL after linking completes }); ``` If you want your users to be able to link a social account with a different email address than the user, or if you want to use a provider that does not return email addresses, you will need to enable this in the account linking settings. ```ts title="auth.ts" const auth = betterAuth({ account: { accountLinking: { allowDifferentEmails: true } }, }); ``` * **Linking Credential-Based Accounts:** To link a credential-based account (e.g., email and password), users can initiate a "forgot password" flow, or you can call the `setPassword` method on the server. ```ts await auth.api.setPassword({ headers: /* headers containing the user's session token */, password: /* new password */ }); ``` `setPassword` can't be called from the client for security reasons. ### Account Unlinking You can unlink a user account by providing a `providerId`. ```ts await authClient.unlinkAccount({ providerId: "google" }); ``` If the `providerId` doesn't exist, it will throw an error. Additionally, if the user only has one account, the unlinking process will fail to prevent account lockout. file: ./content/docs/contribute/areas-to-contribute.mdx meta: { "title": "Areas To Contribute", "description": "Areas to contribute to BetterAuth" } ## Working on Issues The easiest way to start contributing is by tackling existing issues. Here's how: 1. Check our [github issues](https://github.com/better-auth/better-auth/issues) for issues labeled `good first issue`, `help wanted` or `question` 2. Comment on the issue you'd like to work on to avoid duplicate efforts 3. If you've found a bug or have a feature request not listed, please create a new issue: * For bugs: Include steps to reproduce, expected vs actual behavior * For features: Describe the feature and its use cases ## Framework Integrations Better Auth aims to support as many frameworks as possible in the JavaScript ecosystem. Here's how you can help expand our framework support: ### Adding New Framework Support 1. Start with documentation for basic integration 2. Focus on framework-agnostic solutions where possible 3. Keep integrations minimal and maintainable ### Integration Guidelines * Prefer framework-agnostic solutions over framework-specific ones * Keep server-side integrations as thin as possible * All integrations currently live in the main package (packages/better-auth) * Focus on reducing implementation friction while maintaining flexibility ### Current Structure We currently maintain all integrations (frameworks, plugins, utilities) in a single package for simplicity. As the project grows, we may consider splitting these into separate packages. ## Plugin Development Want to extend Better Auth's functionality? Here's our plugin contribution process: ### Core Plugins 1. **Open an Issue First**: Describe your plugin idea 2. **Get Approval**: Wait for team feedback before starting 3. **Development**: Follow our plugin development guide ### Community Plugins * Feel free to develop and publish plugins independently * Use our Plugin docs as a reference * Consider sharing your plugin in our community showcase channel. ## Core Feature Development Contributing to core features requires careful coordination: ### Process 1. **Open an Issue**: Describe the feature and its use cases 2. **Discussion**: Get feedback from the team and community 3. **Planning**: Outline implementation approach 4. **Development**: Create a draft PR early for guidance ### Best Practices * Start with a minimal viable implementation * Include tests and documentation * Keep backwards compatibility in mind * Follow our coding standards ## Security Contributions Security is our top priority. If you discover a security vulnerability: 1. **DO NOT** create a public issue 2. Send details to [security@better-auth.com](mailto:security@better-auth.com) 3. Include: * Description of the vulnerability * Steps to reproduce * Potential impact * Suggested fix (if any) Learn more about our [security issues documentation](https://better-auth.com/docs/contribute/security-issues). ## Other Ways to Contribute * Improve error messages * Add test cases * Enhance documentation * Share examples * Report performance issues * Suggest optimizations Remember, all contributions, no matter how small, are valuable to the project. If you're unsure about anything, don't hesitate to ask in our Discord server. Let's make authentication better, together! 🚀 file: ./content/docs/contribute/database-adapters.mdx meta: { "title": "Database Adapters", "description": "Adding a new database adapter to BetterAuth" } WORKIN IN PROGRESS. file: ./content/docs/contribute/documenting.mdx meta: { "title": "Documenting", "description": "Documentation Guide for Better Auth" } ## Overview Better Auth uses [Fumadocs](https://fumadocs.vercel.app/) with MDX for documentation. This page explains how to add and update documentation effectively. ## Project Structure ## Writing Documentation ### Creating New Pages 1. Add your MDX file in `docs/content/docs/.mdx`: #### File Name Convention \* The file name should be in the format of `some-feature.mdx`. (Kebab-case) \* The file name represents the URL route to that page. For example, if the file name is `some-feature.mdx`, the URL route will be `/docs/some-feature`. \* Place the file in their respective category folder. For example, if the file is in the `guides` category, place it in the `/guides` folder. ```mdx --- title: Your Feature description: Description of your feature --- # Your Feature Introduction to your feature here... ``` 2. Update the sidebar in `docs/components/sidebar-content.tsx`: To find out what icon you would like to use for the sidebar, you can check out the Lucide Icons library. ```tsx export const contents: Content[] = { items: [ { // Existing items... }, { title: "Your Section", icon: , items: [ { title: "Your Feature", icon: , href: "/docs/your-feature", }, ], }, ], }; ``` *** ## MDX Guidelines ### Basic Formatting ```mdx # Main Title ## Section This is a paragraph with **bold** and _italic_ text. ### Subsection - List item 1 - List item 2 1. Order list item 1 2. Order list item 2 ``` ### Components ```mdx Important information goes here Some warning Some info Some error ``` Important information goes here Some warning Some info Some error ```mdx ``` ```mdx Link to Introduction ``` Link to Introduction ```mdx Some content! Some content! ``` Some content! Some content! ```mdx ## Step 1 Hello world! ## Step 2 Hello world! ``` Step 1 Hello world! Step 2 Hello world! ```mdx Javascript is weird Rust is fast ``` Javascript is weird Rust is fast ### Code Blocks Use language-specific syntax highlighting: ````mdx ```typescript import { BetterAuth } from "better-auth"; const auth = new BetterAuth({ // configuration }); ``` ```` Optionally, you can also specify a file name for the code block: ````mdx ```typescript title="auth.ts" import { BetterAuth } from "better-auth"; const auth = new BetterAuth({ // configuration }); ``` ```` You can also highlight specific lines in a codeblock: ````mdx ```typescript title="auth.ts" import { BetterAuth } from "better-auth"; const auth = new BetterAuth({ // [!codeㅤhighlight] // configuration // [!codeㅤhighlight] }); // [!codeㅤhighlight] ``` ```` The result: ```typescript title="auth.ts" import { BetterAuth } from "better-auth"; const auth = new BetterAuth({// [!code highlight] // configuration // [!code highlight] }); // [!code highlight] ``` *** ## Best Practices ### Do's * Keep documentation up-to-date with code changes * Use clear, concise language * Include practical examples * Document error cases and edge conditions * Use proper headings hierarchy * Include cross-references to related docs ### Don'ts * Don't repeat information unnecessarily * Don't leave outdated examples * Don't use complex jargon without explanation * Don't skip documenting breaking changes * Don't leave TODO comments in published docs ## Local Development ```bash # Install dependencies pnpm install # Start docs development server pnpm -F docs dev ``` ## Document Types ### 1. Guides * Step-by-step instructions * Tutorial-style content ### 2. Reference Documentation * API documentation * Configuration options * Type definitions ### 3. Examples * Code snippets * Complete examples * Use cases ## Contributing to Documentation 1. **Find the Right File** * Navigate to `docs/content/docs/` * Locate the relevant MDX file or create a new one 2. **Make Your Changes** * Update or add content * Follow the MDX formatting guidelines * Include examples where appropriate 3. **Test Locally** * Run the development server * Check your changes * Verify links and navigation 4. **Submit Changes** * Create a pull request * Include a clear description of documentation changes * Request review from documentation maintainers ## Getting Help Need assistance with documentation? * Join our Discord server and ask for help! * Open an issue with the `documentation` label Remember: Good documentation is crucial for Better Auth's success. Thank you for helping improve it! 📚 file: ./content/docs/contribute/getting-started.mdx meta: { "title": "Contributing to BetterAuth", "description": "Getting started to contribute to BetterAuth" } Hello and welcome! We're thrilled that you're interested in contributing to Better Auth. Whether you're fixing bugs, adding new features, improving documentation, or just sharing ideas, your contribution helps make Better Auth better for everyone. ## Getting Started Before diving in, here are a few important resources: * Take a look at our existing issues and pull requests * Join our community discussions in Discord ## Development Setup To get started with development: Make sure you have Node.JS{" "} installed, preferably on LTS. ### 1. Fork the repository Visit [https://github.com/better-auth/better-auth](https://github.com/better-auth/better-auth) Click the "Fork" button in the top right. ### 2. Clone your fork ```bash # Replace YOUR-USERNAME with your GitHub username git clone https://github.com/YOUR-USERNAME/better-auth.git cd better-auth ``` ### 3. Install dependencies Make sure you have pnpm installed! ```bash pnpm install ``` ### 4. Prepare ENV files Copy the example env file to create your new `.env` file. ```bash cp -n ./docs/.env.example ./docs/.env ``` ## Making changes Once you have an idea of what you want to contribute, you can start making changes. Here are some steps to get started: ### 1. Create a new branch ```bash # Make sure you're on main git checkout main # Pull latest changes git pull upstream main # Create and switch to a new branch git checkout -b feature/your-feature-name ``` ### 2. Start development server Start the development server: ```bash pnpm dev ``` To start the docs server: ```bash pnpm -F docs dev ``` ### 3. Make Your Changes * Make your changes to the codebase. * Write tests if needed. (Read more about testing here) * Update documentation. (Read more about documenting here) ## Best Practices * Write clear commit messages * Update documentation to reflect your changes * Add tests for new features * Follow our coding standards * Keep pull requests focused on a single change ## Need Help? Don't hesitate to ask for help! You can: * Open an issue with questions * Join our community discussions * Reach out to project maintainers Together, we can make Better Auth an even better authentication solution. Thank you for being part of our community! Your contributions are greatly appreciated, and we look forward to collaborating with you! file: ./content/docs/contribute/security-issues.mdx meta: { "title": "Security Policy and Disclosure Guidelines", "description": "Security Policy and Disclosure Guidelines in BetterAuth" } ## Our Security Commitment At Better Auth, security is our highest priority. We take all security concerns seriously and appreciate the efforts of security researchers and our community in responsibly disclosing potential vulnerabilities. ## Reporting a Vulnerability ### Preferred Method Please report security issues by emailing: ``` security@better-auth.com ``` ### What to Include When reporting a security issue, please include: 1. **Description** * Clear explanation of the vulnerability * Affected versions/components * Type of vulnerability (e.g., XSS, CSRF, Authentication Bypass) 2. **Reproduction Steps** * Detailed steps to reproduce the vulnerability * Any required setup or configuration * Code samples if applicable * Example payload if relevant 3. **Impact Assessment** * Potential security impact * What an attacker might be able to accomplish * Affected user groups or data 4. **Supporting Materials** * Screenshots or videos (if applicable) * Proof of concept code (if available) * Related references or CVEs 5. **Mitigation Suggestions** * Proposed fixes or workarounds * Recommended security controls ## Our Response Process 1. **Initial Response** * Acknowledgment within 24 to 48 hours * Case number assignment * Initial severity assessment 2. **Investigation** * Technical review of the report * Impact analysis * Verification of reproduction steps * Development of fix strategy 3. **Resolution Timeline** * Critical vulnerabilities: 24-48 hours * High severity: 1 week * Medium severity: 2 weeks * Low severity: Next release cycle 4. **Communication** * Regular updates on fix progress * Notification when fix is ready * Coordination on disclosure timeline ## Disclosure Policy 1. **Responsible Disclosure** * No public disclosure before fix implementation * Coordinated release of security advisories * Credit given to reporters in security advisories 2. **Fix Release Process** * Security patches released as priority updates * Clear documentation of fixes * Migration guides if needed 3. **Post-Fix Communication** * Public security advisories * Notification to affected users * Updated security documentation ## Bug Bounty Program Currently, we do not operate a formal bug bounty program. However, we do recognize and credit security researchers who: * Follow responsible disclosure guidelines * Provide clear and actionable reports ## Out of Scope The following are typically out of scope: * DOS/DDOS attacks * Spam attacks * Social engineering * Physical security issues * Issues requiring physical access * Issues in dependencies (report to them directly) * TLS configuration issues without practical impact ## Contact Information * Security Issues: [security@better-auth.com](mailto:security@better-auth.com) Remember: Security is a collaborative effort. Thank you for helping keep Better Auth and its users secure! file: ./content/docs/contribute/testing.mdx meta: { "title": "Testing in Better Auth", "description": "Testing Guide for Better Auth" } ## Overview We use Vitest as our testing framework of choice. Currently, we focus on unit tests, with plans to expand to integration testing in the future. This guide will help you understand our testing philosophy and how to write effective tests. We expect you to have knowledge of Vitest before you start writing tests. If you're new to Vitest, we recommend you read the [Vitest documentation](https://vitest.dev/guide/) first. ## Testing Philosophy Our approach to testing emphasizes: * Simplicity over complexity * Real implementations over mocks * Co-location of tests with source code * Minimal test setup * Clear and readable test cases ## Getting Started ### Setup ```bash # Install dependencies including Vitest pnpm install # Run tests pnpm run test ``` ## Test Structure ### File Organization We follow a co-location strategy for tests. Place your test files next to the source files they test: ### Test File Naming * Use `.test.ts` extension for test files * Name test files after the module they test * For TypeScript, use `.test.ts` or `.test.tsx` for React components ## Writing Tests ### Basic Test Structure This is just example code, and isn't meant to be used as-is. It's meant to demonstrate the structure of a test file. ```ts title="auth.test.ts" //... import { describe, it, expect } from "vitest"; import { getTestInstance } from "./test-utils/test-instance"; describe("Authentication Module", () => { it("should successfully authenticate a user", async () => { const { client } = getTestInstance(); const result = await client.signIn.email({ email: "hello@gmail.com", password: "123456", }); expect(result.success).toBe(true); expect(result.user).toBeDefined(); }); }); ``` ### Using `getTestInstance` The test instance provides multiple handy properties that you can use to test your feature. ```ts import { getTestInstance } from "../test-utils"; const { client, auth, cookieSetter, customFetchImpl, db, sessionSetter, signInWithTestUser, signInWithUser, testUser, } = getTestInstance(); ``` Only create custom instances when needed ```typescript import { getTestInstance } from "../test-utils"; const { auth } = getTestInstance({ customConfig: { // your custom configuration }, }); ``` ## Best Practices ### Do's * Write descriptive test names that explain the expected behavior * Test both success and failure cases * Keep tests focused and atomic * Use setup and teardown when needed * Test edge cases and boundary conditions This is just example code, and isn't meant to be used as-is. It's meant to demonstrate the best Practices of testing. ```typescript describe("User Authentication", () => { it("should reject invalid credentials", async () => { const { client } = getTestInstance(); const result = await client.signIn.email({ email: "invalid@example.com", password: "wrong", }); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); it("should handle empty inputs appropriately", async () => { const { client } = getTestInstance(); const result = await client.signIn.email({ email: "", password: "", }); expect(result.success).toBe(false); expect(result.error).toMatch(/required/); }); }); ``` ### Don'ts * Don't create unnecessary mocks * Don't test implementation details * Don't create separate test folders * Don't write brittle tests * Don't test external libraries ## Future Plans * Implement integration testing * Add end-to-end testing * Expand API testing coverage * Add performance testing * Implement snapshot testing where appropriate ## Contributing When submitting a PR: * Ensure all tests pass * Add tests for new features * Update tests for modified features * Follow existing test patterns * Include meaningful assertions Need help? Feel free to reach out in our Discord server! file: ./content/docs/examples/astro.mdx meta: { "title": "Astro Example", "description": "Better Auth Astro example." } This is an example of how to use Better Auth with Astro. It uses Solid for building the components. **Implements the following features:** Email & Password . Social Sign-in with Google . Passkeys . Email Verification . Password Reset . Two Factor Authentication . Profile Update . Session Management