Files
gitea-mirror/docs/better-auth-docs.md
T
2025-09-14 10:18:37 +05:30

920 KiB
Raw Permalink Blame History

basic-usage: Basic Usage

URL: /docs/basic-usage Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/basic-usage.mdx

Getting started with Better Auth


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, magic link, passkey, email-otp, and more.

Email & Password

To enable email and password authentication:

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.

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.

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.

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.

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.

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]
})

Sign in with social providers

To sign in using a social provider you need to call signIn.social. It takes an object with the following properties:

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.

await authClient.signOut();

you can pass fetchOptions to redirect onSuccess

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.

<Tabs items={["React", "Vue","Svelte", "Solid", "Vanilla"]} defaultValue="react"> ```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" <script setup lang="ts"> import { authClient } from "~/lib/auth-client" // [!code highlight]
const session = authClient.useSession() // [!code highlight]
</script>

<template>
    <div>
        <div>
            <pre>{{ session.data }}</pre>
            <button v-if="session.data" @click="authClient.signOut()">
                Sign out
            </button>
        </div>
    </div>
</template>
```
```svelte title="user.svelte" <script lang="ts"> import { authClient } from "$lib/auth-client"; // [!code highlight]
const session = authClient.useSession(); // [!code highlight]
</script>
<p>
    {$session.data?.user.email}
</p>
```
```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 (
        <pre>{JSON.stringify(session(), null, 2)}</pre>
    );
}
```

Get Session

If you prefer not to use the hook, you can use the getSession method provided by the client.

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.

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

<Tabs items={["Next.js", "Nuxt", "Svelte", "Astro", "Hono", "TanStack"]}> ```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,
});
---
<!-- Your Astro Template -->
```
```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
```

<Callout>
  If you prefer adding the schema manually, you can check the schema required on the [two factor plugin](/docs/plugins/2fa#schema) documentation.
</Callout>
### 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 two factor plugin documentation.

comparison: Comparison

URL: /docs/comparison Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/comparison.mdx

Comparison of Better Auth versus over other auth libraries and services.


title: Comparison description: Comparison of Better Auth versus over other auth libraries and services.

Comparison is the thief of joy.

Here are non detailed reasons why you may want to use Better Auth over other auth libraries and services.

vs Other Auth Libraries

  • Framework agnostic - Works with any framework, not just specific ones
  • Advanced features built-in - 2FA, multi-tenancy, multi-session, rate limiting, and many more
  • Plugin system - Extend functionality without forking or complex workarounds
  • Full control - Customize auth flows exactly how you want

vs Self-Hosted Auth Servers

  • No separate infrastructure - Runs in your app, users stay in your database
  • Zero server maintenance - No auth servers to deploy, monitor, or update
  • Complete feature set - Everything you need without the operational overhead

vs Managed Auth Services

  • Keep your data - Users stay in your database, not a third-party service
  • No per-user costs - Scale without worrying about auth billing
  • Single source of truth - All user data in one place

vs Rolling Your Own

  • Security handled - Battle-tested auth flows and security practices
  • Focus on your product - Spend time on features that matter to your business
  • Plugin extensibility - Add custom features without starting from scratch

installation: Installation

URL: /docs/installation Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/installation.mdx

Learn how to configure Better Auth in your project.


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:

<CodeBlockTabs defaultValue="npm">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="npm">
      npm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="pnpm">
      pnpm
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="yarn">
      yarn
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="bun">
      bun
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="npm">
    ```bash
    npm install better-auth
    ```
  </CodeBlockTab>

  <CodeBlockTab value="pnpm">
    ```bash
    pnpm add better-auth
    ```
  </CodeBlockTab>

  <CodeBlockTab value="yarn">
    ```bash
    yarn add better-auth
    ```
  </CodeBlockTab>

  <CodeBlockTab value="bun">
    ```bash
    bun add better-auth
    ```
  </CodeBlockTab>
</CodeBlockTabs>

<Callout type="info">
  If you're using a separate client and server setup, make sure to install Better Auth in both parts of your project.
</Callout>
### 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=
```

<GenerateSecret />

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, and more!

<Tabs items={["sqlite", "postgres", "mysql"]}>
  <Tab value="sqlite">
    ```ts title="auth.ts"
    import { betterAuth } from "better-auth";
    import Database from "better-sqlite3";

    export const auth = betterAuth({
        database: new Database("./sqlite.db"),
    })
    ```
  </Tab>

  <Tab value="postgres">
    ```ts title="auth.ts"
    import { betterAuth } from "better-auth";
    import { Pool } from "pg";

    export const auth = betterAuth({
        database: new Pool({
            // connection options
        }),
    })
    ```
  </Tab>

  <Tab value="mysql">
    ```ts title="auth.ts"
    import { betterAuth } from "better-auth";
    import { createPool } from "mysql2/promise";

    export const auth = betterAuth({
        database: createPool({
            // connection options
        }),
    })
    ```
  </Tab>
</Tabs>

Alternatively, if you prefer to use an ORM, you can use one of the built-in adapters.

<Tabs items={["drizzle", "prisma", "mongodb"]}>
  <Tab value="drizzle">
    ```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"
        }),
    });
    ```
  </Tab>

  <Tab value="prisma">
    ```ts title="auth.ts"
    import { betterAuth } from "better-auth";
    import { prismaAdapter } from "better-auth/adapters/prisma";
    // If your Prisma file is located elsewhere, you can change the path
    import { PrismaClient } from "@/generated/prisma";

    const prisma = new PrismaClient();
    export const auth = betterAuth({
        database: prismaAdapter(prisma, {
            provider: "sqlite", // or "mysql", "postgresql", ...etc
        }),
    });
    ```
  </Tab>

  <Tab value="mongodb">
    ```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),
    });
    ```
  </Tab>
</Tabs>

<Callout>
  If your database is not listed above, check out our other supported
  [databases](/docs/adapters/other-relational-databases) for more information,
  or use one of the supported ORMs.
</Callout>
### 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.

<Callout>
  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.
</Callout>

```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.

<Callout>
  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).
</Callout>
### 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 as string, // [!code highlight]
      clientSecret: process.env.GITHUB_CLIENT_SECRET as string, // [!code highlight]
    }, // [!code highlight]
  }, // [!code highlight]
});
```

<Callout type="info">
  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.
</Callout>
### 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).

<Callout>
  Better Auth supports any backend framework with standard Request and Response
  objects and offers helper functions for popular frameworks.
</Callout>

<Tabs items={["next-js", "nuxt", "svelte-kit", "remix", "solid-start", "hono", "express", "elysia", "tanstack-start", "expo"]} defaultValue="react">
  <Tab value="next-js">
    ```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);
    ```
  </Tab>

  <Tab value="nuxt">
    ```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));
    });
    ```
  </Tab>

  <Tab value="svelte-kit">
    ```ts title="hooks.server.ts"
    import { auth } from "$lib/auth"; // path to your auth file
    import { svelteKitHandler } from "better-auth/svelte-kit";
    import { building } from '$app/environment'

    export async function handle({ event, resolve }) {
        return svelteKitHandler({ event, resolve, auth, building });
    }
    ```
  </Tab>

  <Tab value="remix">
    ```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)
    }
    ```
  </Tab>

  <Tab value="solid-start">
    ```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);
    ```
  </Tab>

  <Tab value="hono">
    ```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);
    ```
  </Tab>

  <Tab value="express">
    <Callout type="warn">
      ExpressJS v5 introduced breaking changes to route path matching by switching to `path-to-regexp@6`. Wildcard routes like `*` should now be written using the new named syntax, e.g. `/{*any}`, to properly capture catch-all patterns. This ensures compatibility and predictable behavior across routing scenarios.
      See the [Express v5 migration guide](https://expressjs.com/en/guide/migrating-5.html) for details.

      As a result, the implementation in ExpressJS v5 should look like this:

      ```ts
      app.all('/api/auth/{*any}', toNodeHandler(auth));
      ```

      *The name any is arbitrary and can be replaced with any identifier you prefer.*
    </Callout>

    ```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 will also work for any other node server framework like express, fastify, hapi, etc., but may require some modifications. See [fastify guide](/docs/integrations/fastify). Note that CommonJS (cjs) isn't supported.
  </Tab>

  <Tab value="astro">
    ```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);
    };
    ```
  </Tab>

  <Tab value="elysia">
    ```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}`
    );
    ```
  </Tab>

  <Tab value="tanstack-start">
    ```ts title="src/routes/api/auth/$.ts"
    import { auth } from '~/lib/server/auth'
    import { createServerFileRoute } from '@tanstack/react-start/server'

    export const ServerRoute = createServerFileRoute('/api/auth/$').methods({
    GET: ({ request }) => {
        return auth.handler(request)
    },
    POST: ({ request }) => {
        return auth.handler(request)
    },
    });
    ```
  </Tab>

  <Tab value="expo">
    ```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 };
    ```
  </Tab>
</Tabs>
### 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.)

<Callout type="info">
  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`)
</Callout>

<Tabs
  items={["react", "vue", "svelte", "solid",

"vanilla"]} defaultValue="react" > ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/client" export const authClient = createAuthClient({ /** The base URL of the server (optional if you're using the same domain) */ // [!code highlight] baseURL: "http://localhost:3000" // [!code highlight] })

  <Tab value="react" title="lib/auth-client.ts">
    ```ts title="lib/auth-client.ts"
    import { createAuthClient } from "better-auth/react"
    export const authClient = createAuthClient({
        /** The base URL of the server (optional if you're using the same domain) */ // [!code highlight]
        baseURL: "http://localhost:3000" // [!code highlight]
    })
    ```
  </Tab>

  <Tab value="vue" title="lib/auth-client.ts">
    ```ts title="lib/auth-client.ts"
    import { createAuthClient } from "better-auth/vue"
    export const authClient = createAuthClient({
        /** The base URL of the server (optional if you're using the same domain) */ // [!code highlight]
        baseURL: "http://localhost:3000" // [!code highlight]
    })
    ```
  </Tab>

  <Tab value="svelte" title="lib/auth-client.ts">
    ```ts title="lib/auth-client.ts"
    import { createAuthClient } from "better-auth/svelte"
    export const authClient = createAuthClient({
        /** The base URL of the server (optional if you're using the same domain) */ // [!code highlight]
        baseURL: "http://localhost:3000" // [!code highlight]
    })
    ```
  </Tab>

  <Tab value="solid" title="lib/auth-client.ts">
    ```ts title="lib/auth-client.ts"
    import { createAuthClient } from "better-auth/solid"
    export const authClient = createAuthClient({
        /** The base URL of the server (optional if you're using the same domain) */ // [!code highlight]
        baseURL: "http://localhost:3000" // [!code highlight]
    })
    ```
  </Tab>
</Tabs>

<Callout type="info">
  Tip: You can also export specific methods if you prefer:
</Callout>

```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.

introduction: Introduction

URL: /docs/introduction Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/introduction.mdx

Introduction to Better Auth.


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!

LLMs.txt

Better Auth provides an LLMs.txt file that helps AI models understand how to interact with your authentication system. You can find it at https://better-auth.com/llms.txt.

adapters: Community Adapters

URL: /docs/adapters/community-adapters Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/adapters/community-adapters.mdx

Integrate Better Auth with community made database adapters.


title: Community Adapters description: Integrate Better Auth with community made database adapters.

This page showcases a list of recommended community made database adapters. We encourage you to create any missing database adapters and maybe get added to the list!

Adapter Database Dialect Author
convex-better-auth Convex Database erquhart
surreal-better-auth SurrealDB Oskar Gmerek
surrealdb-better-auth Surreal Database Necmttn
better-auth-surrealdb Surreal Database msanchezdev
payload-better-auth Payload CMS forrestdevs
@ronin/better-auth RONIN ronin-co
better-auth-instantdb InstantDB daveycodez
@nerdfolio/remult-better-auth Remult Tai Vo

adapters: Drizzle ORM Adapter

URL: /docs/adapters/drizzle Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/adapters/drizzle.mdx

Integrate Better Auth with Drizzle ORM.


title: Drizzle ORM Adapter description: Integrate Better Auth with Drizzle ORM.

Drizzle ORM is a powerful and flexible ORM for Node.js and TypeScript. It provides a simple and intuitive API for working with databases, and supports a wide range of databases including MySQL, PostgreSQL, SQLite, and more. Read more here: Drizzle ORM.

Example Usage

Make sure you have Drizzle installed and configured. Then, you can use the Drizzle adapter to connect to your database.

import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "./database.ts";

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    // [!code highlight]
    provider: "sqlite", // or "pg" or "mysql" // [!code highlight]
  }), // [!code highlight]
  //... the rest of your config
});

Schema generation & migration

The Better Auth CLI allows you to generate or migrate your database schema based on your Better Auth configuration and plugins.

To generate the schema required by Better Auth, run the following command:

npx @better-auth/cli@latest generate

To generate and apply the migration, run the following commands:

npx drizzle-kit generate # generate the migration file
npx drizzle-kit migrate # apply the migration

Additional Information

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.

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 your tables are using plural form, you can just pass the usePlural option:

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    ...
    usePlural: true,
  }),
});

If you're looking for performance improvements or tips, take a look at our guide to performance optimizations.

adapters: MongoDB Adapter

URL: /docs/adapters/mongo Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/adapters/mongo.mdx

Integrate Better Auth with MongoDB.


title: MongoDB Adapter description: Integrate Better Auth with MongoDB.

MongoDB is a popular NoSQL database that is widely used for building scalable and flexible applications. It provides a flexible schema that allows for easy data modeling and querying. Read more here: MongoDB.

Example Usage

Make sure you have MongoDB installed and configured. Then, you can use the mongodb adapter.

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),
});

Schema generation & migration

For MongoDB, we don't need to generate or migrate the schema.

adapters: MS SQL

URL: /docs/adapters/mssql Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/adapters/mssql.mdx

Integrate Better Auth with MS SQL.


title: MS SQL description: Integrate Better Auth with MS SQL.

Microsoft SQL Server is a relational database management system developed by Microsoft, designed for enterprise-level data storage, management, and analytics with robust security and scalability features. Read more here.

Example Usage

Make sure you have MS SQL installed and configured. Then, you can connect it straight into Better Auth.

import { betterAuth } from "better-auth";
import { MssqlDialect } from "kysely";
import * as Tedious from 'tedious'
import * as Tarn from 'tarn'

const dialect = new MssqlDialect({
  tarn: {
    ...Tarn,
    options: {
      min: 0,
      max: 10,
    },
  },
  tedious: {
    ...Tedious,
    connectionFactory: () => new Tedious.Connection({
      authentication: {
        options: {
          password: 'password',
          userName: 'username',
        },
        type: 'default',
      },
      options: {
        database: 'some_db',
        port: 1433,
        trustServerCertificate: true,
      },
      server: 'localhost',
    }),
  },
})

export const auth = betterAuth({
  database: {
    dialect,
    type: "mssql"
  }
});


For more information, read Kysely's documentation to the [MssqlDialect](https://kysely-org.github.io/kysely-apidoc/classes/MssqlDialect.html).

Schema generation & migration

The Better Auth CLI allows you to generate or migrate your database schema based on your Better Auth configuration and plugins.

  <th>
    <p className="font-bold text-[16px] mb-1">MS SQL Schema Migration</p>
  </th>
</tr>

MS SQL Schema Generation

Supported Supported
npx @better-auth/cli@latest generate
npx @better-auth/cli@latest migrate

Additional Information

MS SQL is supported under the hood via the Kysely adapter, any database supported by Kysely would also be supported. (Read more here)

If you're looking for performance improvements or tips, take a look at our guide to performance optimizations.

adapters: MySQL

URL: /docs/adapters/mysql Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/adapters/mysql.mdx

Integrate Better Auth with MySQL.


title: MySQL description: Integrate Better Auth with MySQL.

MySQL is a popular open-source relational database management system (RDBMS) that is widely used for building web applications and other types of software. It provides a flexible and scalable database solution that allows for efficient storage and retrieval of data. Read more here: MySQL.

Example Usage

Make sure you have MySQL installed and configured. Then, you can connect it straight into Better Auth.

import { betterAuth } from "better-auth";
import { createPool } from "mysql2/promise";

export const auth = betterAuth({
  database: createPool({
    host: "localhost",
    user: "root",
    password: "password",
    database: "database",
  }),
});
For more information, read Kysely's documentation to the [MySQLDialect](https://kysely-org.github.io/kysely-apidoc/classes/MysqlDialect.html).

Schema generation & migration

The Better Auth CLI allows you to generate or migrate your database schema based on your Better Auth configuration and plugins.

  <th>
    <p className="font-bold text-[16px] mb-1">MySQL Schema Migration</p>
  </th>
</tr>

MySQL Schema Generation

Supported Supported
npx @better-auth/cli@latest generate
npx @better-auth/cli@latest migrate

Additional Information

MySQL is supported under the hood via the Kysely adapter, any database supported by Kysely would also be supported. (Read more here)

If you're looking for performance improvements or tips, take a look at our guide to performance optimizations.

adapters: Other Relational Databases

URL: /docs/adapters/other-relational-databases Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/adapters/other-relational-databases.mdx

Integrate Better Auth with other relational databases.


title: Other Relational Databases description: Integrate Better Auth with other relational databases.

Better Auth supports a wide range of database dialects out of the box thanks to Kysely.

Any dialect supported by Kysely can be utilized with Better Auth, including capabilities for generating and migrating database schemas through the CLI.

Core Dialects

Kysely Organization Dialects

Kysely Community dialects

You can see the full list of supported Kysely dialects{" "} here.

adapters: PostgreSQL

URL: /docs/adapters/postgresql Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/adapters/postgresql.mdx

Integrate Better Auth with PostgreSQL.


title: PostgreSQL description: Integrate Better Auth with PostgreSQL.

PostgreSQL is a powerful, open-source relational database management system known for its advanced features, extensibility, and support for complex queries and large datasets. Read more here.

Example Usage

Make sure you have PostgreSQL installed and configured. Then, you can connect it straight into Better Auth.

import { betterAuth } from "better-auth";
import { Pool } from "pg";

export const auth = betterAuth({
  database: new Pool({
    connectionString: "postgres://user:password@localhost:5432/database",
  }),
});
For more information, read Kysely's documentation to the [PostgresDialect](https://kysely-org.github.io/kysely-apidoc/classes/PostgresDialect.html).

Schema generation & migration

The Better Auth CLI allows you to generate or migrate your database schema based on your Better Auth configuration and plugins.

  <th>
    <p className="font-bold text-[16px] mb-1">PostgreSQL Schema Migration</p>
  </th>
</tr>

PostgreSQL Schema Generation

Supported Supported
npx @better-auth/cli@latest generate
npx @better-auth/cli@latest migrate

Additional Information

PostgreSQL is supported under the hood via the Kysely adapter, any database supported by Kysely would also be supported. (Read more here)

If you're looking for performance improvements or tips, take a look at our guide to performance optimizations.

adapters: Prisma

URL: /docs/adapters/prisma Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/adapters/prisma.mdx

Integrate Better Auth with Prisma.


title: Prisma description: Integrate Better Auth with Prisma.

Prisma ORM is an open-source database toolkit that simplifies database access and management in applications by providing a type-safe query builder and an intuitive data modeling interface. Read more here.

Example Usage

Make sure you have Prisma installed and configured. Then, you can use the Prisma adapter to connect to your database.

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",
  }),
});
If you have configured a custom output directory in your `schema.prisma` file (e.g., `output = "../src/generated/prisma"`), make sure to import the Prisma client from that location instead of `@prisma/client`. Learn more about custom output directories in the [Prisma documentation](https://www.prisma.io/docs/guides/nextjs#21-install-prisma-orm-and-create-your-first-models).

Schema generation & migration

The Better Auth CLI allows you to generate or migrate your database schema based on your Better Auth configuration and plugins.

  <th>
    <p className="font-bold text-[16px] mb-1">Prisma Schema Migration</p>
  </th>
</tr>

Prisma Schema Generation

Supported Not Supported
npx @better-auth/cli@latest generate

Additional Information

If you're looking for performance improvements or tips, take a look at our guide to performance optimizations.

adapters: SQLite

URL: /docs/adapters/sqlite Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/adapters/sqlite.mdx

Integrate Better Auth with SQLite.


title: SQLite description: Integrate Better Auth with SQLite.

SQLite is a lightweight, serverless, self-contained SQL database engine that is widely used for local data storage in applications. Read more here.

Example Usage

Better Auth supports multiple SQLite drivers. Choose the one that best fits your environment:

The most popular and stable SQLite driver for Node.js:

import { betterAuth } from "better-auth";
import Database from "better-sqlite3";

export const auth = betterAuth({
  database: new Database("database.sqlite"),
});
For more information, read Kysely's documentation to the [SqliteDialect](https://kysely-org.github.io/kysely-apidoc/classes/SqliteDialect.html).

Node.js Built-in SQLite (Experimental)

The `node:sqlite` module is still experimental and may change at any time. It requires Node.js 22.5.0 or later.

Starting from Node.js 22.5.0, you can use the built-in SQLite module:

import { betterAuth } from "better-auth";
import { DatabaseSync } from "node:sqlite";

export const auth = betterAuth({
  database: new DatabaseSync("database.sqlite"),
});

To run your application with Node.js SQLite:

node your-app.js

Bun Built-in SQLite

You can also use the built-in SQLite module in Bun, which is similar to the Node.js version:

import { betterAuth } from "better-auth";
import { Database } from "bun:sqlite";
export const auth = betterAuth({
  database: new Database("database.sqlite"),
});

Schema generation & migration

The Better Auth CLI allows you to generate or migrate your database schema based on your Better Auth configuration and plugins.

  <th>
    <p className="font-bold text-[16px] mb-1">SQLite Schema Migration</p>
  </th>
</tr>

SQLite Schema Generation

Supported Supported
npx @better-auth/cli@latest generate
npx @better-auth/cli@latest migrate

Additional Information

SQLite is supported under the hood via the Kysely adapter, any database supported by Kysely would also be supported. (Read more here)

If you're looking for performance improvements or tips, take a look at our guide to performance optimizations.

authentication: Apple

URL: /docs/authentication/apple Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/apple.mdx

Apple provider setup and usage.


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).

You will need an active **Apple Developer account** to access the developer portal and generate these credentials.

Follow these steps to set up your App ID, Service ID, and generate the key needed for your client secret:

1. **Navigate to Certificates, Identifiers & Profiles:**
   In the Apple Developer Portal, go to the "Certificates, Identifiers & Profiles" section.

2. **Create an App ID:**
   * Go to the `Identifiers` tab.
   * Click the `+` icon next to Identifiers.
   * Select `App IDs`, then click `Continue`.
   * Select `App` as the type, then click `Continue`.
   * **Description:** Enter a name for your app (e.g., "My Awesome App"). This name may be displayed to users when they sign in.
   * **Bundle ID:** Set a bundle ID. The recommended format is a reverse domain name (e.g., `com.yourcompany.yourapp`). Using a suffix like `.ai` (for app identifier) can help with organization but is not required (e.g., `com.yourcompany.yourapp.ai`).
   * Scroll down to **Capabilities**. Select the checkbox for `Sign In with Apple`.
   * Click `Continue`, then `Register`.

3. **Create a Service ID:**
   * Go back to the `Identifiers` tab.
   * Click the `+` icon.
   * Select `Service IDs`, then click `Continue`.
   * **Description:** Enter a description for this service (e.g., your app name again).
   * **Identifier:** Set a unique identifier for the service. Use a reverse domain format, distinct from your App ID (e.g., `com.yourcompany.yourapp.si`, where `.si` indicates service identifier - this is for your organization and not required). **This Service ID will be your `clientId`.**
   * Click `Continue`, then `Register`.

4. **Configure the Service ID:**
   * Find the Service ID you just created in the `Identifiers` list and click on it.
   * Check the `Sign In with Apple` capability, then click `Configure`.
   * Under **Primary App ID**, select the App ID you created earlier (e.g., `com.yourcompany.yourapp.ai`).
   * Under **Domains and Subdomains**, list all the root domains you will use for Sign In with Apple (e.g., `example.com`, `anotherdomain.com`).
   * Under **Return URLs**, enter the callback URL. `https://yourdomain.com/api/auth/callback/apple`. Add all necessary return URLs.
   * Click `Next`, then `Done`.
   * Click `Continue`, then `Save`.

5. **Create a Client Secret Key:**
   * Go to the `Keys` tab.
   * Click the `+` icon to create a new key.
   * **Key Name:** Enter a name for the key (e.g., "Sign In with Apple Key").
   * Scroll down and select the checkbox for `Sign In with Apple`.
   * Click the `Configure` button next to `Sign In with Apple`.
   * Select the **Primary App ID** you created earlier.
   * Click `Save`, then `Continue`, then `Register`.
   * **Download the Key:** Immediately download the `.p8` key file. **This file is only available for download once.** Note the Key ID (available on the Keys page after creation) and your Team ID (available in your Apple Developer Account settings).

6. **Generate the Client Secret (JWT):**
   Apple requires a JSON Web Token (JWT) to be generated dynamically using the downloaded `.p8` key, the Key ID, and your Team ID. This JWT serves as your `clientSecret`.

   You can use the guide below from [Apple's documentation](https://developer.apple.com/documentation/accountorganizationaldatasharing/creating-a-client-secret) to understand how to generate this client secret. You can also use our built in generator [below](#generate-apple-client-secret-jwt) to generate the client secret JWT required for 'Sign in with Apple'.
### Configure the provider
To configure the provider, you need to add it to the `socialProviders` option of the auth instance.

You also need to add `https://appleid.apple.com` to the `trustedOrigins` array in your auth instance configuration to allow communication with Apple's authentication servers.

```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]
    },
    // Add appleid.apple.com to trustedOrigins for Sign In with Apple flows
    trustedOrigins: ["https://appleid.apple.com"], // [!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.
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.
await authClient.signIn.social({
    provider: "apple",
    idToken: {
        token: // Apple ID Token,
        nonce: // Nonce (optional)
        accessToken: // Access Token (optional)
    }
})

Generate Apple Client Secret (JWT)

authentication: Atlassian

URL: /docs/authentication/atlassian Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/atlassian.mdx

Atlassian provider setup and usage.


title: Atlassian description: Atlassian provider setup and usage.

### Get your Credentials
1. Sign in to your Atlassian account and go to the [Atlassian Developer Console](https://developer.atlassian.com/console/myapps/)
2. Click "Create new app"
3. Fill out the app details
4. Configure your redirect URI (e.g., `https://yourdomain.com/api/auth/callback/atlassian`)
5. Note your Client ID and Client Secret

<Callout type="info">
  * The default scope is `read:jira-user` and `offline_access`. For additional scopes, refer to the [Atlassian OAuth documentation](https://developer.atlassian.com/cloud/confluence/oauth-2-3lo-apps/).
</Callout>

Make sure to set the redirect URI to match your application's callback URL. If you change the base path of the auth routes, you should update the redirect URI 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: {
        atlassian: { // [!code highlight]
            clientId: process.env.ATLASSIAN_CLIENT_ID as string, // [!code highlight]
            clientSecret: process.env.ATLASSIAN_CLIENT_SECRET as string, // [!code highlight]
        }, // [!code highlight]
    },
})
```
### Sign In with Atlassian
To sign in with Atlassian, 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 `atlassian`.

```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient =  createAuthClient()

const signIn = async () => {
    const data = await authClient.signIn.social({
        provider: "atlassian"
    })
}
```

<Callout type="info">
  For more information about Atlassian's OAuth scopes and API capabilities, refer to the [official Atlassian OAuth 2.0 (3LO) apps documentation](https://developer.atlassian.com/cloud/confluence/oauth-2-3lo-apps/).
</Callout>

authentication: Cognito

URL: /docs/authentication/cognito Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/cognito.mdx

Amazon Cognito provider setup and usage.


title: Cognito description: Amazon Cognito provider setup and usage.

### Get your Cognito Credentials
To integrate with Cognito, you need to set up a **User Pool** and an **App client** in the [Amazon Cognito Console](https://console.aws.amazon.com/cognito/).

Follow these steps:

1. Go to the **Cognito Console** and create a **User Pool**.
2. Under **App clients**, create a new **App client** (note the Client ID and Client Secret if enabled).
3. Go to **Domain** and set a Cognito Hosted UI domain (e.g., `your-app.auth.us-east-1.amazoncognito.com`).
4. In **App client settings**, enable:
   * Allowed OAuth flows: `Authorization code grant`
   * Allowed OAuth scopes: `openid`, `profile`, `email`
5. Add your callback URL (e.g., `http://localhost:3000/api/auth/callback/cognito`).

<Callout type="info">
  * **User Pool is required** for Cognito authentication.
  * Make sure the callback URL matches exactly what you configure in Cognito.
</Callout>
### Configure the provider
Configure the `cognito` key in `socialProviders` key of your `auth` instance.

```ts title="auth.ts"
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  socialProviders: {
    cognito: {
      clientId: process.env.COGNITO_CLIENT_ID as string, // [!code highlight]
      clientSecret: process.env.COGNITO_CLIENT_SECRET as string, // [!code highlight]
      domain: process.env.COGNITO_DOMAIN as string, // e.g. "your-app.auth.us-east-1.amazoncognito.com" [!code highlight]
      region: process.env.COGNITO_REGION as string, // e.g. "us-east-1" [!code highlight]
      userPoolId: process.env.COGNITO_USERPOOL_ID as string, // [!code highlight]
    },
  },
})
```
### Sign In with Cognito
To sign in with Cognito, use the `signIn.social` function from the client.

```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"

const authClient = createAuthClient()

const signIn = async () => {
  const data = await authClient.signIn.social({
    provider: "cognito"
  })
}
```

### Additional Options:

* `scope`: Additional OAuth2 scopes to request (combined with default permissions).
  * Default: `"openid" "profile" "email"`
  * Common Cognito scopes:
    * `openid`: Required for OpenID Connect authentication
    * `profile`: Access to basic profile info
    * `email`: Access to users email
    * `phone`: Access to users phone number
    * `aws.cognito.signin.user.admin`: Grants access to Cognito-specific APIs
* Note: You must configure the scopes in your Cognito App Client settings. [available scopes](https://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html#token-endpoint-userinfo)
* `getUserInfo`: Custom function to retrieve user information from the Cognito UserInfo endpoint.

<Callout type="info">
  For more information about Amazon Cognito's scopes and API capabilities, refer to the [official documentation](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-define-resource-servers.html?utm_source).
</Callout>

authentication: Discord

URL: /docs/authentication/discord Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/discord.mdx

Discord provider setup and usage.


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"
    })
}
```

authentication: Dropbox

URL: /docs/authentication/dropbox Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/dropbox.mdx

Dropbox provider setup and usage.


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.

### 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"
    })
}
```

authentication: Email & Password

URL: /docs/authentication/email-password Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/email-password.mdx

Implementing email and password authentication with Better Auth.


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.

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.

Client Side

const { data, error } = await authClient.signUp.email({
    name: John Doe,
    email: john.doe@example.com,
    password: password1234,
    image: https://example.com/image.png, // required
    callbackURL: https://example.com/callback, // required
});

Server Side

const data = await auth.api.signUpEmail({
    body: {
        name: John Doe,
        email: john.doe@example.com,
        password: password1234,
        image: https://example.com/image.png, // required
        callbackURL: https://example.com/callback, // required
    }
});

Type Definition

type signUpEmail = {
    /**
     * The name of the user.
     */
    name: string = "John Doe"
    /**
     * The email address of the user.
     */
    email: string = "john.doe@example.com"
    /**
     * The password of the user. It should be at least 8 characters long and max 128 by default.
     */
    password: string = "password1234"
    /**
     * An optional profile image of the user.
     */
    image?: string = "https://example.com/image.png"
    /**
     * An optional URL to redirect to after the user signs up.
     */
    callbackURL?: string = "https://example.com/callback"

}
These are the default properties for the sign up email endpoint, however it's possible that with [additional fields](/docs/concepts/typescript#additional-fields) or special plugins you can pass more properties to the endpoint.

Sign In

To sign a user in, you can use the signIn.email function provided by the client.

Client Side

const { data, error } = await authClient.signIn.email({
    email: john.doe@example.com,
    password: password1234,
    rememberMe, // required
    callbackURL: https://example.com/callback, // required
});

Server Side

const data = await auth.api.signInEmail({
    body: {
        email: john.doe@example.com,
        password: password1234,
        rememberMe, // required
        callbackURL: https://example.com/callback, // required
    },
    // This endpoint requires session cookies.
    headers: await headers()
});

Type Definition

type signInEmail = {
    /**
     * The email address of the user.
     */
    email: string = "john.doe@example.com"
    /**
     * The password of the user. It should be at least 8 characters long and max 128 by default.
     */
    password: string = "password1234"
    /**
     * If false, the user will be signed out when the browser is closed. (optional) (default: true)
     */
    rememberMe?: boolean = true
    /**
     * An optional URL to redirect to after the user signs in. (optional)
     */
    callbackURL?: string = "https://example.com/callback"

}
These are the default properties for the sign in email endpoint, however it's possible that with [additional fields](/docs/concepts/typescript#additional-fields) or special plugins you can pass different properties to the endpoint.

Sign Out

To sign a user out, you can use the signOut function provided by the client.

Client Side

const { data, error } = await authClient.signOut({});

Server Side

await auth.api.signOut({

    // This endpoint requires session cookies.
    headers: await headers()
});

Type Definition

type signOut = {

}

you can pass fetchOptions to redirect onSuccess

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.

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.
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.

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.

await authClient.sendVerificationEmail({
  email: "user@email.com",
  callbackURL: "/", // The redirect URL after verification
});

Request Password Reset

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.

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}`,
      });
    },
    onPasswordReset: async ({ user }, request) => {
      // your logic here
      console.log(`Password for user ${user.email} has been reset.`);
    },
  },
});

Additionally, you can provide an onPasswordReset callback to execute logic after a password has been successfully reset.

Once you configured your server you can call requestPasswordReset function to send reset password link to user. If the user exists, it will trigger the sendResetPassword function you provided in the auth config.

Client Side

const { data, error } = await authClient.requestPasswordReset({
    email: john.doe@example.com,
    redirectTo: https://example.com/reset-password, // required
});

Server Side

const data = await auth.api.requestPasswordReset({
    body: {
        email: john.doe@example.com,
        redirectTo: https://example.com/reset-password, // required
    }
});

Type Definition

type requestPasswordReset = {
    /**
     * The email address of the user to send a password reset email to 
     */
    email: string = "john.doe@example.com"
    /**
     * The URL to redirect the user to reset their password. If the token isn't valid or expired, it'll be redirected with a query parameter `?error=INVALID_TOKEN`. If the token is valid, it'll be redirected with a query parameter `?token=VALID_TOKEN 
     */
    redirectTo?: string = "https://example.com/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.
const { data, error } = await authClient.resetPassword({
  newPassword: "password1234",
  token,
});

Client Side

const { data, error } = await authClient.resetPassword({
    newPassword: password1234,
    token,
});

Server Side

const data = await auth.api.resetPassword({
    body: {
        newPassword: password1234,
        token,
    }
});

Type Definition

type resetPassword = {
    /**
     * The new password to set 
     */
    newPassword: string = "password1234"
    /**
     * The token to reset the password 
     */
    token: string

}

Update password

A user's password 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 one of the following approaches:

Client Side

const { data, error } = await authClient.changePassword({
    newPassword: newpassword1234,
    currentPassword: oldpassword1234,
    revokeOtherSessions, // required
});

Server Side

const data = await auth.api.changePassword({
    body: {
        newPassword: newpassword1234,
        currentPassword: oldpassword1234,
        revokeOtherSessions, // required
    },
    // This endpoint requires session cookies.
    headers: await headers()
});

Type Definition

type changePassword = {
    /**
     * The new password to set 
     */
    newPassword: string = "newpassword1234"
    /**
     * The current user password 
     */
    currentPassword: string = "oldpassword1234"
    /**
     * When set to true, all other active sessions for this user will be invalidated
     */
    revokeOtherSessions?: boolean = true

}

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.

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
        }
    }
})

<TypeTable type={{ enabled: { description: "Enable email and password authentication.", type: "boolean", default: "false", }, disableSignUp: { description: "Disable email and password sign up.", type: "boolean", default: "false" }, minPasswordLength: { description: "The minimum length of a password.", type: "number", default: 8, }, maxPasswordLength: { description: "The maximum length of a password.", type: "number", default: 128, }, sendResetPassword: { description: "Sends a password reset email. It takes a function that takes two parameters: token and user.", type: "function", }, onPasswordReset: { description: "A callback function that is triggered when a user's password is changed successfully.", type: "function", }, resetPasswordTokenExpiresIn: { description: "Number of seconds the reset password token is valid for.", type: "number", default: 3600 }, password: { description: "Password configuration.", type: "object", properties: { hash: { description: "custom password hashing function", type: "function", }, verify: { description: "custom password verification function", type: "function", }, }, }, }} />

authentication: Facebook

URL: /docs/authentication/facebook Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/facebook.mdx

Facebook provider setup and usage.


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`.

<Callout type="warn">
  Avoid exposing the `clientSecret` in client-side code (e.g., frontend apps) because its sensitive information.
</Callout>

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]
    },
})
```

<Callout>
  BetterAuth also supports Facebook Login for Business, all you need
  to do is provide the `configId` as listed in **Facebook Login For Business > Configurations** alongside your `clientId` and `clientSecret`. Note that the app must be a Business app and, since BetterAuth expects to have an email address and account id, the configuration must be of the "User access token" type. "System-user access token" is not supported.
</Callout>
### 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:

export const auth = betterAuth({
    socialProviders: {
        facebook: {
            clientId: process.env.FACEBOOK_CLIENT_ID as string,
            clientSecret: process.env.FACEBOOK_CLIENT_SECRET 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].

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.

authentication: Figma

URL: /docs/authentication/figma Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/figma.mdx

Figma provider setup and usage.


title: Figma description: Figma provider setup and usage.

### Get your Credentials
1. Sign in to your Figma account and go to the [Developer Apps page](https://www.figma.com/developers/apps)
2. Click "Create new app"
3. Fill out the app details (name, description, etc.)
4. Configure your redirect URI (e.g., `https://yourdomain.com/api/auth/callback/figma`)
5. Note your Client ID and Client Secret

<Callout type="info">
  * The default scope is `file_read`. For additional scopes like `file_write`, refer to the [Figma OAuth documentation](https://www.figma.com/developers/api#oauth2).
</Callout>

Make sure to set the redirect URI to match your application's callback URL. If you change the base path of the auth routes, you should update the redirect URI 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: {
        figma: { // [!code highlight]
            clientId: process.env.FIGMA_CLIENT_ID as string, // [!code highlight]
            clientSecret: process.env.FIGMA_CLIENT_SECRET as string, // [!code highlight]
            clientKey: process.env.FIGMA_CLIENT_KEY as string, // [!code highlight]
        }, // [!code highlight]
    },
})
```
### Sign In with Figma
To sign in with Figma, 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 `figma`.

```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient =  createAuthClient()

const signIn = async () => {
    const data = await authClient.signIn.social({
        provider: "figma"
    })
}
```

<Callout type="info">
  For more information about Figma's OAuth scopes and API capabilities, refer to the [official Figma API documentation](https://www.figma.com/developers/api).
</Callout>

authentication: GitHub

URL: /docs/authentication/github Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/github.mdx

GitHub provider setup and usage.


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.

Important: You MUST include the user:email scope in your GitHub app. See details below.
### 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"
    })
}
```

Usage

Setting up your Github app

Github has two types of apps: Github apps and OAuth apps.

For OAuth apps, you don't have to do anything special (just follow the steps above). For Github apps, you DO have to add one more thing, which is enable it to read the user's email:

  1. After creating your app, go to Permissions and Events > Account Permissions > Email Addresses and select "Read-Only"

  2. Save changes.

That's all! Now you can copy the Client ID and Client Secret of your app!

If you get "email\_not\_found" error, it's because you selected a Github app & did not configure this part!

Why don't I have a refresh token?

Github doesn't issue refresh tokens for OAuth apps. For regular OAuth apps, GitHub issues access tokens that remain valid indefinitely unless the user revokes them, the app revokes them, or they go unused for a year. There's no need for a refresh token because the access token doesn't expire on a short interval like Google or Discord.

authentication: GitLab

URL: /docs/authentication/gitlab Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/gitlab.mdx

GitLab provider setup and usage.


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"
    })
}
```

authentication: Google

URL: /docs/authentication/google Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/google.mdx

Google provider setup and usage.


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.
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.
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.

Always ask to select an account

If you want to always ask the user to select an account, you pass the prompt parameter to the provider, setting it to select_account.

socialProviders: {
    google: {
        prompt: "select_account", // [!code highlight]
        clientId: process.env.GOOGLE_CLIENT_ID as string,
        clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
    },
}

Requesting Additional Google Scopes

If your application needs additional Google scopes after the user has already signed up (e.g., for Google Drive, Gmail, or other Google services), you can request them using the linkSocial method with the same Google provider.

const requestGoogleDriveAccess = async () => {
  await authClient.linkSocial({
    provider: "google",
    scopes: ["https://www.googleapis.com/auth/drive.file"],
  });
};

// Example usage in a React component
return (
  <button onClick={requestGoogleDriveAccess}>
    Add Google Drive Permissions
  </button>
);

This will trigger a new OAuth flow that requests the additional scopes. After completion, your account will have the new scope in the database, and the access token will give you access to the requested Google APIs.

Ensure you're using Better Auth version 1.2.7 or later to avoid "Social account already linked" errors when requesting additional scopes from the same provider.

Always get refresh token

Google only issues a refresh token the first time a user consents to your app. If the user has already authorized your app, subsequent OAuth flows will only return an access token, not a refresh token.

To always get a refresh token, you can set the accessType to offline, and prompt to select_account consent in the provider options.

socialProviders: {
    google: {
        clientId: process.env.GOOGLE_CLIENT_ID as string,
        clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
        accessType: "offline", // [!code highlight]
        prompt: "select_account consent", // [!code highlight]
    },
}
**Revoking Access:** If you want to get a new refresh token for a user who has already authorized your app, you must have them revoke your app's access in their Google account settings, then re-authorize.

authentication: Hugging Face

URL: /docs/authentication/huggingface Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/huggingface.mdx

Hugging Face provider setup and usage.


title: Hugging Face description: Hugging Face provider setup and usage.

### Get your Hugging Face credentials
To use Hugging Face sign in, you need a client ID and client secret. [Hugging Face OAuth documentation](https://huggingface.co/docs/hub/oauth). Make sure the created oauth app on Hugging Face has the "email" scope.

Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/huggingface` 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: {
        huggingface: { // [!code highlight]
            clientId: process.env.HUGGINGFACE_CLIENT_ID as string, // [!code highlight]
            clientSecret: process.env.HUGGINGFACE_CLIENT_SECRET as string, // [!code highlight]
        }, // [!code highlight]
    },
})
```
### Sign In with Hugging Face
To sign in with Hugging Face, 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 `huggingface`.

```ts title="auth-client.ts"  
import { createAuthClient } from "better-auth/client"
const authClient =  createAuthClient()

const signIn = async () => {
    const data = await authClient.signIn.social({
        provider: "huggingface"
    })
}
```

authentication: Kakao

URL: /docs/authentication/kakao Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/kakao.mdx

Kakao provider setup and usage.


title: Kakao description: Kakao provider setup and usage.

### Get your Kakao Credentials
To use Kakao sign in, you need a client ID and client secret. You can get them from the [Kakao Developer Portal](https://developers.kakao.com).

Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/kakao` 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: {
        kakao: { // [!code highlight]
            clientId: process.env.KAKAO_CLIENT_ID as string, // [!code highlight]
            clientSecret: process.env.KAKAO_CLIENT_SECRET as string, // [!code highlight]
        }, // [!code highlight]
    }
})
```
### Sign In with Kakao
To sign in with Kakao, 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 `kakao`.

```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient =  createAuthClient()

const signIn = async () => {
    const data = await authClient.signIn.social({
        provider: "kakao"
    })
}
```

authentication: Kick

URL: /docs/authentication/kick Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/kick.mdx

Kick provider setup and usage.


title: Kick description: Kick provider setup and usage.

### Get your Kick Credentials
To use Kick sign in, you need a client ID and client secret. You can get them from the [Kick Developer Portal](https://kick.com/settings/developer).

Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/kick` 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: {
        kick: { // [!code highlight]
            clientId: process.env.KICK_CLIENT_ID as string, // [!code highlight]
            clientSecret: process.env.KICK_CLIENT_SECRET as string, // [!code highlight]
        }, // [!code highlight]
    }
})
```
### Sign In with Kick
To sign in with Kick, 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 `kick`.

```ts title="auth-client.ts"  
import { createAuthClient } from "better-auth/client"
const authClient =  createAuthClient()

const signIn = async () => {
    const data = await authClient.signIn.social({
        provider: "kick"
    })
}
```

authentication: LINE

URL: /docs/authentication/line Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/line.mdx

LINE provider setup and usage.


title: LINE description: LINE provider setup and usage.

### Get your LINE credentials
1. Create a channel in the LINE Developers Console.
2. Note your Channel ID (client\_id) and Channel secret (client\_secret).
3. In the channel settings, add your Redirect URI, e.g. `http://localhost:3000/api/auth/callback/line` for local development.
4. Enable required scopes (at least `openid`; add `profile`, `email` if you need name, avatar, email).

See LINE Login v2.1 reference for details: \[`https://developers.line.biz/en/reference/line-login/#issue-access-token`]
### Configure the provider
Add your LINE credentials to `socialProviders.line` in your auth configuration.

```ts title="auth.ts"
import { betterAuth } from "better-auth";

export const auth = betterAuth({
  socialProviders: {
    line: {
      clientId: process.env.LINE_CLIENT_ID as string,
      clientSecret: process.env.LINE_CLIENT_SECRET as string,
      // Optional: override redirect if needed
      // redirectURI: "https://your.app/api/auth/callback/line",
      // scopes are prefilled: ["openid","profile","email"]. Append if needed
    },
  },
});
```

Usage

Sign In with LINE

Use the client signIn.social with provider: "line".

import { createAuthClient } from "better-auth/client";
const authClient = createAuthClient();

async function signInWithLINE() {
  const res = await authClient.signIn.social({ provider: "line" });
}

Sign In with LINE using ID Token (optional)

If you obtain the LINE ID token on the client, you can sign in directly without redirection.

await authClient.signIn.social({
  provider: "line",
  idToken: {
    token: "<LINE_ID_TOKEN>",
    accessToken: "<LINE_ACCESS_TOKEN>",
  },
});

Notes

  • Default scopes include openid profile email. Adjust as needed via provider options.
  • Verify redirect URI exactly matches the value configured in LINE Developers Console.
  • LINE ID token verification uses the official endpoint and checks audience and optional nonce per spec.

Designing a login button? Follow LINE's button guidelines.

authentication: Linear

URL: /docs/authentication/linear Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/linear.mdx

Linear provider setup and usage.


title: Linear description: Linear provider setup and usage.

### Get your Linear credentials
To use Linear sign in, you need a client ID and client secret. You can get them from the [Linear Developer Portal](https://linear.app/settings/api).

Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/linear` 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.

When creating your OAuth application in Linear, you'll need to specify the required scopes. The default scope is `read`, but you can also request additional scopes like `write` if needed.
### 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: {
        linear: { // [!code highlight]
            clientId: process.env.LINEAR_CLIENT_ID as string, // [!code highlight]
            clientSecret: process.env.LINEAR_CLIENT_SECRET as string, // [!code highlight]
        }, // [!code highlight]
    },
})
```
### Sign In with Linear
To sign in with Linear, 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 `linear`.

```ts title="auth-client.ts"  
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient()

const signIn = async () => {
    const data = await authClient.signIn.social({
        provider: "linear"
    })
}
```
### Available scopes
Linear OAuth supports the following scopes:

* `read` (default): Read access for the user's account
* `write`: Write access for the user's account
* `issues:create`: Allows creating new issues and their attachments
* `comments:create`: Allows creating new issue comments
* `timeSchedule:write`: Allows creating and modifying time schedules
* `admin`: Full access to admin level endpoints (use with caution)

You can specify additional scopes when configuring the provider:

```ts title="auth.ts"
export const auth = betterAuth({
    socialProviders: {
        linear: {
            clientId: process.env.LINEAR_CLIENT_ID as string,
            clientSecret: process.env.LINEAR_CLIENT_SECRET as string,
            scope: ["read", "write"] // [!code highlight]
        },
    },
})
```

authentication: LinkedIn

URL: /docs/authentication/linkedin Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/linkedin.mdx

LinkedIn Provider


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) Sign In with LinkedIn using OpenID Connect

### 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"
    })
}
```

authentication: Microsoft

URL: /docs/authentication/microsoft Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/microsoft.mdx

Microsoft provider setup and usage.


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]                
            authority: "https://login.microsoftonline.com", // Authentication authority URL // [!code highlight]
            prompt: "select_account", // Forces account selection // [!code highlight]
        }, // [!code highlight]
    },
})
```

**Authority URL**: Use the default `https://login.microsoftonline.com` for standard Entra ID scenarios or `https://<tenant-id>.ciamlogin.com` for CIAM (Customer Identity and Access Management) scenarios.

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.
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
  });
};

authentication: Naver

URL: /docs/authentication/naver Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/naver.mdx

Naver provider setup and usage.


title: Naver description: Naver provider setup and usage.

### Get your Naver Credentials
To use Naver sign in, you need a client ID and client secret. You can get them from the [Naver Developers](https://developers.naver.com/).

Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/naver` 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: {
        naver: { // [!code highlight]
            clientId: process.env.NAVER_CLIENT_ID as string, // [!code highlight]
            clientSecret: process.env.NAVER_CLIENT_SECRET as string, // [!code highlight]
        }, // [!code highlight]
    }
})
```
### Sign In with Naver
To sign in with Naver, 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 `naver`.

```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient =  createAuthClient()

const signIn = async () => {
    const data = await authClient.signIn.social({
        provider: "naver"
    })
}
```

authentication: Notion

URL: /docs/authentication/notion Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/notion.mdx

Notion provider setup and usage.


title: Notion description: Notion provider setup and usage.

### Get your Notion credentials
To use Notion as a social provider, you need to get your Notion OAuth credentials. You can get them by creating a new integration in the [Notion Developers Portal](https://www.notion.so/my-integrations).

In the Notion integration settings > OAuth Domain & URIs, make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/notion` for local development. For production, make sure to set the redirect URL as your application domain, e.g. `https://example.com/api/auth/callback/notion`. If you change the base path of the auth routes, you should update the redirect URL accordingly.

<Callout>
  Make sure your Notion integration has the appropriate capabilities enabled. For user authentication, you'll need the "Read user information including email addresses" capability.
</Callout>
### Configure the provider
To configure the provider, you need to pass the `clientId` and `clientSecret` to `socialProviders.notion` in your auth configuration.

```ts title="auth.ts"   
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    socialProviders: {
        notion: { // [!code highlight]
            clientId: process.env.NOTION_CLIENT_ID as string, // [!code highlight]
            clientSecret: process.env.NOTION_CLIENT_SECRET as string, // [!code highlight]
        }, // [!code highlight]
    },
})
```

Usage

Sign In with Notion

To sign in with Notion, 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 notion.
import { createAuthClient } from "better-auth/client"
const authClient =  createAuthClient()

const signIn = async () => {
    const data = await authClient.signIn.social({
        provider: "notion"
    })
}

Notion Integration Types

Notion supports different integration types. When creating your integration, you can choose between:

  • Public integrations: Can be installed by any Notion workspace
  • Internal integrations: Limited to your own workspace

For most authentication use cases, you'll want to create a public integration to allow users from different workspaces to sign in.

Requesting Additional Notion Scopes

If your application needs additional Notion capabilities after the user has already signed up, you can request them using the linkSocial method with the same Notion provider and additional scopes.

const requestNotionAccess = async () => {
    await authClient.linkSocial({
        provider: "notion",
        // Notion automatically provides access based on integration capabilities
    });
};

// Example usage in a React component
return <button onClick={requestNotionAccess}>Connect Notion Workspace</button>;
After authentication, you can use the access token to interact with the Notion API to read and write pages, databases, and other content that the user has granted access to.

authentication: Other Social Providers

URL: /docs/authentication/other-social-providers Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/other-social-providers.mdx

Other social providers setup and usage.


title: Other Social Providers description: Other social providers setup and usage.

Better Auth providers out of the box support for the Generic Oauth Plugin 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.

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

Instagram Example

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"],
        },
      ],
    }),
  ],
});
const response = await authClient.signIn.oauth2({
  providerId: "instagram",
  callbackURL: "/dashboard", // the path to redirect to after the user is authenticated
});

Coinbase Example

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...
        },
      ],
    }),
  ],
});
const response = await authClient.signIn.oauth2({
  providerId: "coinbase",
  callbackURL: "/dashboard", // the path to redirect to after the user is authenticated
});

authentication: PayPal

URL: /docs/authentication/paypal Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/paypal.mdx

Paypal provider setup and usage.


title: PayPal description: Paypal provider setup and usage.

### Get your PayPal Credentials
To integrate with PayPal, you need to obtain API credentials by creating an application in the [PayPal Developer Portal](https://developer.paypal.com/dashboard).

Follow these steps:

1. Create an account on the PayPal Developer Portal
2. Create a new application, [official docs](https://developer.paypal.com/developer/applications/)
3. Configure Log in with PayPal under "Other features"
4. Set up your Return URL (redirect URL)
5. Configure user information permissions
6. Note your Client ID and Client Secret

<Callout type="info">
  * PayPal has two environments: Sandbox (for testing) and Live (for production)
  * For testing, create sandbox test accounts in the Developer Dashboard under "Sandbox" → "Accounts"
  * You cannot use your real PayPal account to test in sandbox mode - you must use the generated test accounts
  * The Return URL in your PayPal app settings must exactly match your redirect URI
  * The PayPal API does not work with localhost. You need to use a public domain for the redirect URL and HTTPS for local testing. You can use [NGROK](https://ngrok.com/) or another similar tool for this.
</Callout>

Make sure to configure "Log in with PayPal" in your app settings:

1. Go to your app in the Developer Dashboard
2. Under "Other features", check "Log in with PayPal"
3. Click "Advanced Settings"
4. Enter your Return URL
5. Select the user information you want to access (email, name, etc.)
6. Enter Privacy Policy and User Agreement URLs

<Callout type="info">
  * PayPal doesn't use traditional OAuth2 scopes in the authorization URL. Instead, you configure permissions directly in the Developer Dashboard
  * For live apps, PayPal must review and approve your application before it can go live, which typically takes a few weeks
</Callout>
### 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: {
        paypal: { // [!code highlight]
            clientId: process.env.PAYPAL_CLIENT_ID as string, // [!code highlight]
            clientSecret: process.env.PAYPAL_CLIENT_SECRET as string, // [!code highlight]
            environment: "sandbox", // or "live" for production //, // [!code highlight]
        }, // [!code highlight]
    },
})
```

#### Options

The PayPal provider accepts the following options:

* `environment`: `'sandbox' | 'live'` - PayPal environment to use (default: `'sandbox'`)
* `requestShippingAddress`: `boolean` - Whether to request shipping address information (default: `false`)

```ts title="auth.ts"
export const auth = betterAuth({
    socialProviders: {
        paypal: {
            clientId: process.env.PAYPAL_CLIENT_ID as string,
            clientSecret: process.env.PAYPAL_CLIENT_SECRET as string,
            environment: "live", // Use "live" for production
            requestShippingAddress: true, // Request address info
        },
    },
})
```
### Sign In with PayPal
To sign in with PayPal, 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 `paypal`.

```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient =  createAuthClient()

const signIn = async () => {
    const data = await authClient.signIn.social({
        provider: "paypal"
    })
}
```

### Additional Options:

* `environment`: PayPal environment to use.
  * Default: `"sandbox"`
  * Options: `"sandbox"` | `"live"`
* `requestShippingAddress`: Whether to request shipping address information.
  * Default: `false`
* `scope`: Additional scopes to request (combined with default permissions).
  * Default: Configured in PayPal Developer Dashboard
  * Note: PayPal doesn't use traditional OAuth2 scopes - permissions are set in the Dashboard
    For more details refer to the [Scopes Reference](https://developer.paypal.com/docs/log-in-with-paypal/integrate/reference/#scope-attributes)
* `mapProfileToUser`: Custom function to map PayPal profile data to user object.
* `getUserInfo`: Custom function to retrieve user information.
  For more details refer to the [User Reference](https://developer.paypal.com/docs/api/identity/v1/#userinfo_get)
* `verifyIdToken`: Custom ID token verification function.

authentication: Reddit

URL: /docs/authentication/reddit Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/reddit.mdx

Reddit provider setup and usage.


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:

export const auth = betterAuth({
    socialProviders: {
        reddit: {
            clientId: process.env.REDDIT_CLIENT_ID as string,
            clientSecret: process.env.REDDIT_CLIENT_SECRET as string,
            duration: "permanent",
            scope: ["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.

authentication: Roblox

URL: /docs/authentication/roblox Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/roblox.mdx

Roblox provider setup and usage.


title: Roblox description: Roblox provider setup and usage.

### Get your Roblox Credentials
Get your Roblox credentials from the [Roblox Creator Hub](https://create.roblox.com/dashboard/credentials?activeTab=OAuthTab).

Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/roblox` 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.

<Callout type="info">
  The Roblox API does not provide email addresses. As a workaround, the user's `email` field uses the `preferred_username` value instead.
</Callout>
### 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: {
        roblox: { // [!code highlight]
            clientId: process.env.ROBLOX_CLIENT_ID as string, // [!code highlight]
            clientSecret: process.env.ROBLOX_CLIENT_SECRET as string, // [!code highlight]
        }, // [!code highlight]
    },
})
```
### Sign In with Roblox
To sign in with Roblox, 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 `roblox`.

```ts title="auth-client.ts"  
import { createAuthClient } from "better-auth/client"
const authClient =  createAuthClient()

const signIn = async () => {
    const data = await authClient.signIn.social({
        provider: "roblox"
    })
}
```

authentication: Salesforce

URL: /docs/authentication/salesforce Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/salesforce.mdx

Salesforce provider setup and usage.


title: Salesforce description: Salesforce provider setup and usage.

### Get your Salesforce Credentials
1. Log into your Salesforce org (Production or Developer Edition)
2. Navigate to **Setup > App Manager**
3. Click **New Connected App**
4. Fill in the basic information:
   * Connected App Name: Your app name
   * API Name: Auto-generated from app name
   * Contact Email: Your email address
5. Enable OAuth Settings:
   * Check **Enable OAuth Settings**
   * Set **Callback URL** to your redirect URI (e.g., `http://localhost:3000/api/auth/callback/salesforce` for development)
   * Select Required OAuth Scopes:
     * Access your basic information (id)
     * Access your identity URL service (openid)
     * Access your email address (email)
     * Perform requests on your behalf at any time (refresh\_token, offline\_access)
6. Enable **Require Proof Key for Code Exchange (PKCE)** (required)
7. Save and note your **Consumer Key** (Client ID) and **Consumer Secret** (Client Secret)

<Callout type="info">
  * For development, you can use `http://localhost:3000` URLs, but production requires HTTPS
  * The callback URL must exactly match what's configured in Better Auth
  * PKCE (Proof Key for Code Exchange) is required by Salesforce and is automatically handled by the provider
</Callout>

<Callout type="warning">
  For sandbox testing, you can create the Connected App in your sandbox org, or use the same Connected App but specify `environment: "sandbox"` in the provider configuration.
</Callout>
### 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: {
        salesforce: { // [!code highlight]
            clientId: process.env.SALESFORCE_CLIENT_ID as string, // [!code highlight]
            clientSecret: process.env.SALESFORCE_CLIENT_SECRET as string, // [!code highlight]
            environment: "production", // or "sandbox" // [!code highlight]
        }, // [!code highlight]
    },
})
```

#### Configuration Options

* `clientId`: Your Connected App's Consumer Key
* `clientSecret`: Your Connected App's Consumer Secret
* `environment`: `"production"` (default) or `"sandbox"`
* `loginUrl`: Custom My Domain URL (without `https://`) - overrides environment setting
* `redirectURI`: Override the auto-generated redirect URI if needed

#### Advanced Configuration

```ts title="auth.ts"
export const auth = betterAuth({
    socialProviders: {
        salesforce: {
            clientId: process.env.SALESFORCE_CLIENT_ID as string,
            clientSecret: process.env.SALESFORCE_CLIENT_SECRET as string,
            environment: "sandbox", // [!code highlight]
            loginUrl: "mycompany.my.salesforce.com", // Custom My Domain // [!code highlight]
            redirectURI: "http://localhost:3000/api/auth/callback/salesforce", // Override if needed // [!code highlight]
        },
    },
})
```

<Callout type="info">
  * Use `environment: "sandbox"` for testing with Salesforce sandbox orgs
  * The `loginUrl` option is useful for organizations with My Domain enabled
  * The `redirectURI` option helps resolve redirect URI mismatch errors
</Callout>
### Environment Variables
Add the following environment variables to your `.env.local` file:

```bash title=".env.local"
SALESFORCE_CLIENT_ID=your_consumer_key_here
SALESFORCE_CLIENT_SECRET=your_consumer_secret_here
BETTER_AUTH_URL=http://localhost:3000 # Important for redirect URI generation
```

For production:

```bash title=".env"
SALESFORCE_CLIENT_ID=your_consumer_key_here
SALESFORCE_CLIENT_SECRET=your_consumer_secret_here
BETTER_AUTH_URL=https://yourdomain.com
```
### Sign In with Salesforce
To sign in with Salesforce, 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 `salesforce`.

```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient()

const signIn = async () => {
    const data = await authClient.signIn.social({
        provider: "salesforce"
    })
}
```
### Troubleshooting
#### Redirect URI Mismatch Error

If you encounter a `redirect_uri_mismatch` error:

1. **Check Callback URL**: Ensure the Callback URL in your Salesforce Connected App exactly matches your Better Auth callback URL
2. **Protocol**: Make sure you're using the same protocol (`http://` vs `https://`)
3. **Port**: Verify the port number matches (e.g., `:3000`)
4. **Override if needed**: Use the `redirectURI` option to explicitly set the redirect URI

```ts
salesforce: {
    clientId: process.env.SALESFORCE_CLIENT_ID as string,
    clientSecret: process.env.SALESFORCE_CLIENT_SECRET as string,
    redirectURI: "http://localhost:3000/api/auth/callback/salesforce", // [!code highlight]
}
```

#### Environment Issues

* **Production**: Use `environment: "production"` (default) with `login.salesforce.com`
* **Sandbox**: Use `environment: "sandbox"` with `test.salesforce.com`
* **My Domain**: Use `loginUrl: "yourcompany.my.salesforce.com"` for custom domains

#### PKCE Requirements

Salesforce requires PKCE (Proof Key for Code Exchange) which is automatically handled by this provider. Make sure PKCE is enabled in your Connected App settings.

<Callout type="info">
  The default scopes requested are `openid`, `email`, and `profile`. The provider will automatically include the `id` scope for accessing basic user information.
</Callout>

authentication: Slack

URL: /docs/authentication/slack Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/slack.mdx

Slack provider setup and usage.


title: Slack description: Slack provider setup and usage.

### Get your Slack credentials
To use Slack as a social provider, you need to create a Slack app and get your credentials.

1. Go to [Your Apps on Slack API](https://api.slack.com/apps) and click "Create New App"
2. Choose "From scratch" and give your app a name and select a development workspace
3. In your app settings, navigate to "OAuth & Permissions"
4. Under "Redirect URLs", add your redirect URL:
   * For local development: `http://localhost:3000/api/auth/callback/slack`
   * For production: `https://yourdomain.com/api/auth/callback/slack`
5. Copy your Client ID and Client Secret from the "Basic Information" page

<Callout>
  Slack requires HTTPS for redirect URLs in production. For local development, you can use tools like [ngrok](https://ngrok.com/) to create a secure tunnel.
</Callout>
### Configure the provider
To configure the provider, you need to pass the `clientId` and `clientSecret` to `socialProviders.slack` in your auth configuration.

```ts title="auth.ts"
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    socialProviders: {
        slack: { // [!code highlight]
            clientId: process.env.SLACK_CLIENT_ID as string, // [!code highlight]
            clientSecret: process.env.SLACK_CLIENT_SECRET as string, // [!code highlight]
        }, // [!code highlight]
    },
})
```

Usage

Sign In with Slack

To sign in with Slack, 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 slack.
import { createAuthClient } from "better-auth/client";
const authClient = createAuthClient();

const signIn = async () => {
  const data = await authClient.signIn.social({ provider: "slack" });
};

Requesting Additional Scopes

By default, Slack uses OpenID Connect scopes: openid, profile, and email. You can request additional Slack scopes during sign-in:

const signInWithSlack = async () => {
  await authClient.signIn.social({
    provider: "slack",
    scopes: ["channels:read", "chat:write"], // Additional Slack API scopes
  });
};

Workspace-Specific Sign In

If you want to restrict sign-in to a specific Slack workspace, you can pass the team parameter:

socialProviders: {
    slack: {
        clientId: process.env.SLACK_CLIENT_ID as string,
        clientSecret: process.env.SLACK_CLIENT_SECRET as string,
        team: "T1234567890", // Your Slack workspace ID
    },
}

Using Slack API After Sign In

After successful authentication, you can access the user's Slack information through the session. The access token can be used to make requests to the Slack API:

const session = await authClient.getSession();
if (session?.user) {
  // Access Slack-specific data
  const slackUserId = session.user.id; // This is the Slack user ID
  // The access token is stored securely on the server
}
The Slack provider uses OpenID Connect by default, which provides basic user information. If you need to access other Slack APIs, make sure to request the appropriate scopes during sign-in.

authentication: Spotify

URL: /docs/authentication/spotify Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/spotify.mdx

Spotify provider setup and usage.


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"
    })
}
```

authentication: TikTok

URL: /docs/authentication/tiktok Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/tiktok.mdx

TikTok provider setup and usage.


title: TikTok description: TikTok provider setup and usage.

### Get your TikTok Credentials
To integrate with TikTok, you need to obtain API credentials by creating an application in the [TikTok Developer Portal](https://developers.tiktok.com/apps).

Follow these steps:

1. Create an account on the TikTok Developer Portal
2. Create a new application
3. Set up a sandbox environment for testing
4. Configure your redirect URL (must be HTTPS)
5. Note your Client Secret and Client Key

<Callout type="info">
  * The TikTok API does not work with localhost. You need to use a public domain for the redirect URL and HTTPS for local testing. You can use [NGROK](https://ngrok.com/) or another similar tool for this.
  * For testing, you will need to use the [Sandbox mode](https://developers.tiktok.com/blog/introducing-sandbox), which you can enable in the TikTok Developer Portal.
  * The default scope is `user.info.profile`. For additional scopes, refer to the [Available Scopes](https://developers.tiktok.com/doc/tiktok-api-scopes/) documentation.
</Callout>

Make sure to set the redirect URL to a valid HTTPS domain 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.

<Callout type="info">
  * The TikTok API does not provide email addresses. As a workaround, this implementation uses the user's `username` value for the `email` field, which is why it requires the `user.info.profile` scope instead of just `user.info.basic`.
  * For production use, you will need to request approval from TikTok for the scopes you intend to use.
</Callout>
### 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: {
        tiktok: { // [!code highlight]
            clientSecret: process.env.TIKTOK_CLIENT_SECRET as string, // [!code highlight]
            clientKey: process.env.TIKTOK_CLIENT_KEY as string, // [!code highlight]
        }, // [!code highlight]
    },
})
```
### Sign In with TikTok
To sign in with TikTok, 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 `tiktok`.

```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient =  createAuthClient()

const signIn = async () => {
    const data = await authClient.signIn.social({
        provider: "tiktok"
    })
}
```

authentication: Twitch

URL: /docs/authentication/twitch Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/twitch.mdx

Twitch provider setup and usage.


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"
    })
}
```

authentication: Twitter (X)

URL: /docs/authentication/twitter Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/twitter.mdx

Twitter provider setup and usage.


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.

<Callout type="info">
  Twitter API v2 now supports email address retrieval. Make sure to request the `user.email` scope when configuring your Twitter app to enable this feature.
</Callout>
### 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 as string, // [!code highlight]
            clientSecret: process.env.TWITTER_CLIENT_SECRET as string, // [!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"
    })
}
```

authentication: VK

URL: /docs/authentication/vk Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/vk.mdx

VK ID Provider


title: VK description: VK ID Provider

### Get your VK ID credentials
To use VK ID sign in, you need a client ID and client secret. You can get them from the [VK ID Developer Portal](https://id.vk.com/about/business/go/docs).

Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/vk` 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: {
    vk: { // [!code highlight]
      clientId: process.env.VK_CLIENT_ID as string, // [!code highlight]
      clientSecret: process.env.VK_CLIENT_SECRET as string, // [!code highlight]
    },
  },
});
```
### Sign In with VK
To sign in with VK, 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 `vk`.

```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client";
const authClient = createAuthClient();

const signIn = async () => {
  const data = await authClient.signIn.social({
    provider: "vk",
  });
};
```

authentication: Zoom

URL: /docs/authentication/zoom Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/authentication/zoom.mdx

Zoom provider setup and usage.


title: Zoom description: Zoom provider setup and usage.

### Create a Zoom App from Marketplace
1. Visit [Zoom Marketplace](https://marketplace.zoom.us).

2. Hover on the `Develop` button and select `Build App`

3. Select `General App` and click `Create`
### Configure your Zoom App
Ensure that you are in the `Basic Information` of your app settings.

1. Under `Select how the app is managed`, choose `User-managed`

2. Under `App Credentials`, copy your `Client ID` and `Client Secret` and store them in a safe location

3. Under `OAuth Information` -> `OAuth Redirect URL`, add your Callback URL. For example,

   ```
   http://localhost:3000/api/auth/callback/zoom
   ```

   <Callout>
     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.
   </Callout>

Skip to the `Scopes` section, then

1. Click the `Add Scopes` button
2. Search for `user:read:user` (View a user) and select it
3. Add any other scopes your applications needs and click `Done`
### 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: {
    zoom: { // [!code highlight]
      clientId: process.env.ZOOM_CLIENT_ID as string, // [!code highlight]
      clientSecret: process.env.ZOOM_CLIENT_SECRET as string, // [!code highlight]
    }, // [!code highlight]
  },
})
```
### Sign In with Zoom
To sign in with Zoom, you can use the `signIn.social` function provided by the client.
You will need to specify `zoom` as the provider.

```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient =  createAuthClient()

const signIn = async () => {
  const data = await authClient.signIn.social({
    provider: "zoom"
  })
}
```

concepts: API

URL: /docs/concepts/api Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/concepts/api.mdx

Better Auth API.


title: API description: Better Auth API.

When you create a new Better Auth instance, it provides you with an api object. This object exposes every endpoint that exist in your Better Auth instance. And you can use this to interact with Better Auth server side.

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.

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: await headers() // some endpoint might require headers
})

Body, Headers, Query

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 query parameters.

await auth.api.getSession({
    headers: await headers()
})

await auth.api.signInEmail({
    body: {
        email: "john@doe.com",
        password: "password"
    },
    headers: await headers() // optional but would be useful to get the user IP, user agent, etc.
})

await auth.api.verifyEmail({
    query: {
        token: "my_token"
    }
})
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 headers and Response Object

When you invoke an API endpoint on the server, it will return a standard JavaScript object or array directly as it's just a regular function call.

But there are times where you might want to get the headers or the Response object instead. For example, if you need to get the cookies or the headers.

Getting headers

To get the headers, you can pass the returnHeaders option to the endpoint.

const { headers, response } = await auth.api.signUpEmail({
	returnHeaders: true,
	body: {
		email: "john@doe.com",
		password: "password",
		name: "John Doe",
	},
});

The headers will be a Headers object. Which you can use to get the cookies or the headers.

const cookies = headers.get("set-cookie");
const headers = headers.get("x-custom-header");

Getting Response Object

To get the Response object, you can pass the asResponse option to the endpoint.

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.

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)
    }
}

concepts: CLI

URL: /docs/concepts/cli Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/concepts/cli.mdx

Built-in CLI for managing your project.


title: CLI description: Built-in CLI for managing your project.

Better Auth comes with a built-in CLI to help you manage the database schemas, initialize your project, generate a secret key for your application, and gather diagnostic information about your setup.

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.

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 auth.ts file in ./, ./utils, ./lib, or any of these directories under src directory.
  • --yes - 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. For other adapters, you'll need to apply the schema using your ORM's migration tool.

npx @better-auth/cli@latest migrate

Options

  • --config - The path to your Better Auth config file. By default, the CLI will search for a auth.ts file in ./, ./utils, ./lib, or any of these directories under src directory.
  • --yes - Skip the confirmation prompt and apply the schema directly.

Init

The init command allows you to initialize Better Auth in your project.

npx @better-auth/cli@latest init

Options

  • --name - The name of your application. (Defaults to your package.json's name property.)
  • --framework - The framework your codebase is using. Currently, the only supported framework is nextjs.
  • --plugins - The plugins you want to use. You can specify multiple plugins by separating them with a comma.
  • --database - The database you want to use. Currently, the only supported database is sqlite.
  • --package-manager - The package manager you want to use. Currently, the only supported package managers are npm, pnpm, yarn, bun. (Defaults to the manager you used to initialize the CLI.)

Info

The info command provides diagnostic information about your Better Auth setup and environment. Useful for debugging and sharing when seeking support.

npx @better-auth/cli@latest info

Output

The command displays:

  • System: OS, CPU, memory, Node.js version
  • Package Manager: Detected manager and version
  • Better Auth: Version and configuration (sensitive data auto-redacted)
  • Frameworks: Detected frameworks (Next.js, React, Vue, etc.)
  • Databases: Database clients and ORMs (Prisma, Drizzle, etc.)

Options

  • --config - Path to your Better Auth config file
  • --json - Output as JSON for sharing or programmatic use

Examples

# Basic usage
npx @better-auth/cli@latest info

# Custom config path
npx @better-auth/cli@latest info --config ./config/auth.ts

# JSON output
npx @better-auth/cli@latest info --json > auth-info.json

Sensitive data like secrets, API keys, and database URLs are automatically replaced with [REDACTED] for safe sharing.

Secret

The CLI also provides a way to generate a secret key for your Better Auth instance.

npx @better-auth/cli@latest secret

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.

concepts: Client

URL: /docs/concepts/client Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/concepts/client.mdx

Better Auth client library for authentication.


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.

npm
<CodeBlockTabsTrigger value="pnpm">
  pnpm
</CodeBlockTabsTrigger>

<CodeBlockTabsTrigger value="yarn">
  yarn
</CodeBlockTabsTrigger>

<CodeBlockTabsTrigger value="bun">
  bun
</CodeBlockTabsTrigger>
```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`)

<Tabs items={["react", "vue", "svelte", "solid", "vanilla"]} defaultValue="react"

```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

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

<Tabs items={["React", "Vue","Svelte", "Solid"]} defaultValue="react"> ```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" <script lang="ts" setup> import { authClient } from '@/lib/auth-client' const session = authClient.useSession() </script>
Continue with GitHub
{{ session.data }}
Sign out
``` ```svelte title="user.svelte" <script lang="ts"> import { client } from "$lib/client"; const session = client.useSession(); </script>
<div
    style="display: flex; flex-direction: column; gap: 10px; border-radius: 10px; border: 1px solid #4B453F; padding: 20px; margin-top: 10px;"
>
    <div>
    {#if $session}
        <div>
        <p>
            {$session?.data?.user.name}
        </p>
        <p>
            {$session?.data?.user.email}
        </p>
        <button
            onclick={async () => {
            await authClient.signOut();
            }}
        >
            Signout
        </button>
        </div>
    {:else}
        <button
        onclick={async () => {
            await authClient.signIn.social({
            provider: "github",
            });
        }}
        >
        Continue with GitHub
        </button>
    {/if}
    </div>
</div>
```
```tsx title="user.tsx" import { client } from "~/lib/client"; import { Show } from 'solid-js';
export default function Home() {
    const session = client.useSession()
    return (
        <Show
            when={session()}
            fallback={<button onClick={toggle}>Log in</button>}
        >
            <button onClick={toggle}>Log out</button>
        </Show>
    ); 
}
```

Fetch Options

The client uses a library called better fetch 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.

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.

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.
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.


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.

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.

const authClient = createAuthClient();

type ErrorTypes = Partial<
	Record<
		keyof typeof authClient.$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

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.

await authClient.signIn.magicLink({
    email: "test@email.com"
})

concepts: Cookies

URL: /docs/concepts/cookies Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/concepts/cookies.mdx

Learn how cookies are used in Better Auth.


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.

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.

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.

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 auth.example.com, and if you authenticate on auth.example.com, you may want to access the same session on app.example.com.

The `domain` attribute controls which domains can access the cookie. Setting it to your root domain (e.g. `example.com`) makes the cookie accessible across all subdomains. For security, you should:
  1. Only enable cross-subdomain cookies if it's necessary
  2. Set the domain to the most specific scope needed (e.g. app.example.com instead of .example.com)
  3. Be cautious of untrusted subdomains that could potentially access these cookies
  4. Consider using separate domains for untrusted services (e.g. status.company.com vs app.company.com)
import { betterAuth } from "better-auth"

export const auth = betterAuth({
    advanced: {
        crossSubDomainCookies: {
            enabled: true,
            domain: "app.example.com", // your domain
        },
    },
    trustedOrigins: [
        'https://example.com',
        'https://app1.example.com',
        'https://app2.example.com',
    ],
})

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.

import { betterAuth } from "better-auth"

export const auth = betterAuth({
    advanced: {
        useSecureCookies: true
    }
})

concepts: Database

URL: /docs/concepts/database Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/concepts/database.mdx

Learn how to use a database with Better Auth.


title: Database description: Learn how to use a database with Better Auth.

Adapters

Better Auth requires a database connection to store data. 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 in the database options. You can learn more about supported database adapters in the Other relational databases documentation.

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.

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.

npx @better-auth/cli generate

See the 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:

interface SecondaryStorage {
  get: (key: string) => Promise<unknown>; 
  set: (key: string, value: string, ttl?: number) => Promise<void>;
  delete: (key: string) => Promise<void>;
}

Then, provide your implementation to the betterAuth function:

betterAuth({
  // ... other options
  secondaryStorage: {
    // Your implementation here
  },
});

Example: Redis Implementation

Here's a basic example using Redis:

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) => {
			return await redis.get(key);
		},
		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

<DatabaseTable fields={[ { name: "id", type: "string", description: "Unique identifier for each user", isPrimaryKey: true, }, { name: "name", type: "string", description: "User's chosen display name", }, { name: "email", type: "string", description: "User's email address for communication and login", }, { name: "emailVerified", type: "boolean", description: "Whether the user's email is verified", }, { name: "image", type: "string", description: "User's image url", isOptional: true, }, { name: "createdAt", type: "Date", description: "Timestamp of when the user account was created", }, { name: "updatedAt", type: "Date", description: "Timestamp of the last update to the user's information", }, ]} />

Session

Table Name: session

<DatabaseTable fields={[ { name: "id", type: "string", description: "Unique identifier for each session", isPrimaryKey: true, }, { name: "userId", type: "string", description: "The ID of the user", isForeignKey: true, }, { name: "token", type: "string", description: "The unique session token", isUnique: true, }, { name: "expiresAt", type: "Date", description: "The time when the session expires", }, { name: "ipAddress", type: "string", description: "The IP address of the device", isOptional: true, }, { name: "userAgent", type: "string", description: "The user agent information of the device", isOptional: true, }, { name: "createdAt", type: "Date", description: "Timestamp of when the session was created", }, { name: "updatedAt", type: "Date", description: "Timestamp of when the session was updated", }, ]} />

Account

Table Name: account

<DatabaseTable fields={[ { name: "id", type: "string", description: "Unique identifier for each account", isPrimaryKey: true, }, { name: "userId", type: "string", description: "The ID of the user", isForeignKey: true, }, { name: "accountId", type: "string", description: "The ID of the account as provided by the SSO or equal to userId for credential accounts", }, { name: "providerId", type: "string", description: "The ID of the provider", }, { name: "accessToken", type: "string", description: "The access token of the account. Returned by the provider", isOptional: true, }, { name: "refreshToken", type: "string", description: "The refresh token of the account. Returned by the provider", isOptional: true, }, { name: "accessTokenExpiresAt", type: "Date", description: "The time when the access token expires", isOptional: true, }, { name: "refreshTokenExpiresAt", type: "Date", description: "The time when the refresh token expires", isOptional: true, }, { name: "scope", type: "string", description: "The scope of the account. Returned by the provider", isOptional: true, }, { name: "idToken", type: "string", description: "The ID token returned from the provider", isOptional: true, }, { name: "password", type: "string", description: "The password of the account. Mainly used for email and password authentication", isOptional: true, }, { name: "createdAt", type: "Date", description: "Timestamp of when the account was created", }, { name: "updatedAt", type: "Date", description: "Timestamp of when the account was updated", }, ]} />

Verification

Table Name: verification

<DatabaseTable fields={[ { name: "id", type: "string", description: "Unique identifier for each verification", isPrimaryKey: true, }, { name: "identifier", type: "string", description: "The identifier for the verification request", }, { name: "value", type: "string", description: "The value to be verified", }, { name: "expiresAt", type: "Date", description: "The time when the verification request expires", }, { name: "createdAt", type: "Date", description: "Timestamp of when the verification request was created", }, { name: "updatedAt", type: "Date", description: "Timestamp of when the verification request was updated", }, ]} />

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:

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:

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:

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.

//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

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.database.generateId option in your auth config.

You can also disable ID generation by setting the advanced.database.generateId option to false. This will assume your database will generate the ID automatically.

Example: Automatic Database IDs

import { betterAuth } from "better-auth";
import { db } from "./db";

export const auth = betterAuth({
  database: {
    db: db,
  },
  advanced: {
    database: {
      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 original 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

import { betterAuth } from "better-auth";

export const auth = betterAuth({
  databaseHooks: {
    user: {
      create: {
        before: async (user, ctx) => {
          // 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.

import { betterAuth } from "better-auth";
import { APIError } from "better-auth/api";

export const auth = betterAuth({
  databaseHooks: {
    user: {
      create: {
        before: async (user, ctx) => {
          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,
          };
        },
      },
    },
  },
});

Using the Context Object

The context object (ctx), passed as the second argument to the hook, contains useful information. For update hooks, this includes the current session, which you can use to access the logged-in user's details.

import { betterAuth } from "better-auth";

export const auth = betterAuth({
  databaseHooks: {
    user: {
      update: {
        before: async (data, ctx) => {
          // You can access the session from the context object.
          if (ctx.context.session) {
            console.log("User update initiated by:", ctx.context.session.userId);
          }
          return { data };
        },
      },
    },
  },
});

Much like standard hooks, database hooks also provide a ctx object that offers a variety of useful properties. Learn more in the Hooks Documentation.

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.

concepts: Email

URL: /docs/concepts/email Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/concepts/email.mdx

Learn how to use email with Better Auth.


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. In this guide, you'll get a walk through of how to implement token based email verification in your app. To use otp based email verification, check out the OTP Verification guide.

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.

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 several ways:

1. During Sign-up

To automatically send a verification email at signup, set emailVerification.sendOnSignUp to true.

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.
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.

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.

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.

await authClient.verifyEmail({
    query: {
        token: "" // Pass the token here
    }
})

Auto Sign In After Verification

To sign in the user automatically after they successfully verify their email, set the autoSignInAfterVerification option to true:

const auth = betterAuth({
    //...your other options
    emailVerification: {
        autoSignInAfterVerification: true
    }
})

Callback after successful email verification

You can run custom code immediately after a user verifies their email using the afterEmailVerification callback. This is useful for any side-effects you want to trigger, like granting access to special features or logging the event.

The afterEmailVerification function runs automatically when a user's email is confirmed, receiving the user object and request details so you can perform actions for that specific user.

Here's how you can set it up:

import { betterAuth } from 'better-auth';

export const auth = betterAuth({
    emailVerification: {
        async afterEmailVerification(user, request) {
            // Your custom logic here, e.g., grant access to premium features
            console.log(`${user.email} has been successfully verified!`);
        }
    }
})

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.

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 guide for more details on how to implement password reset in your app. Also you can check out the Otp verification guide for how to implement password reset with OTP in your app.

concepts: Hooks

URL: /docs/concepts/hooks Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/concepts/hooks.mdx

Better Auth Hooks let you customize BetterAuth's behavior


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:

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:

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

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:

const hook = createAuthMiddleware(async (ctx) => {
    return ctx.json({
        message: "Hello World",
    });
});

Redirects

Use ctx.redirect to redirect users:

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:

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:

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.

createAuthMiddleware(async (ctx) => {
    const newSession = ctx.context.newSession
});

Returned

The returned value from the hook is passed to the next hook in the chain.

createAuthMiddleware(async (ctx) => {
    const returned = ctx.context.returned; //this could be a successful response or an APIError
});

Response Headers

The response headers added by endpoints and hooks that run before this hook.

createAuthMiddleware(async (ctx) => {
    const responseHeaders = ctx.context.responseHeaders;
});

Predefined Auth Cookies

Access BetterAuths predefined cookie properties:

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.

concepts: OAuth

URL: /docs/concepts/oauth Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/concepts/oauth.mdx

How Better Auth handles OAuth


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 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:

import { betterAuth } from "better-auth";

export const auth = betterAuth({
  // Other configurations...
  socialProviders: {
    google: {
      clientId: "YOUR_GOOGLE_CLIENT_ID",
      clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
    },
  },
});

Usage

Sign In

To sign in with a social provider, you can use the signIn.social function with the authClient or auth.api for server-side usage.

// client-side usage
await authClient.signIn.social({
  provider: "google", // or any other provider id
})
// server-side usage
await auth.api.signInSocial({
  body: {
    provider: "google", // or any other provider id
  },
});

To link an account to a social provider, you can use the linkAccount function with the authClient or auth.api for server-side usage.

await authClient.linkSocial({
  provider: "google", // or any other provider id
})

server-side usage:

await auth.api.linkSocialAccount({
  body: {
    provider: "google", // or any other provider id
  },
  headers: // pass headers with authenticated token
});

Get Access Token

To get the access token for a social provider, you can use the getAccessToken function with the authClient or auth.api for server-side usage. When you use this endpoint, if the access token is expired, it will be refreshed.

const { accessToken } = await authClient.getAccessToken({
  providerId: "google", // or any other provider id
  accountId: "accountId", // optional, if you want to get the access token for a specific account
})

server-side usage:

await auth.api.getAccessToken({
  body: {
    providerId: "google", // or any other provider id
    accountId: "accountId", // optional, if you want to get the access token for a specific account
    userId: "userId", // optional, if you don't provide headers with authenticated token
  },
  headers: // pass headers with authenticated token
});

Get Account Info Provided by the provider

To get provider specific account info you can use the accountInfo function with the authClient or auth.api for server-side usage.

const info = await authClient.accountInfo({
  accountId: "accountId", // here you pass in the provider given account id, the provider is automatically detected from the account id
})

server-side usage:

await auth.api.accountInfo({
  body: { accountId: "accountId" },
  headers: // pass headers with authenticated token
});

Requesting Additional Scopes

Sometimes your application may need additional OAuth scopes after the user has already signed up (e.g., for accessing GitHub repositories or Google Drive). Users may not want to grant extensive permissions initially, preferring to start with minimal permissions and grant additional access as needed.

You can request additional scopes by using the linkSocial method with the same provider. This will trigger a new OAuth flow that requests the additional scopes while maintaining the existing account connection.

const requestAdditionalScopes = async () => {
    await authClient.linkSocial({
        provider: "google",
        scopes: ["https://www.googleapis.com/auth/drive.file"],
    });
};
Make sure you're running Better Auth version 1.2.7 or later. Earlier versions (like 1.2.2) may show a "Social account already linked" error when trying to link with an existing provider for additional scopes.

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}.

disableImplicitSignUp: Disables implicit sign-up. In order to sign up a user, requestSignUp needs to be set to true when signing in.

disableSignUp: Disables sign-up for new users.

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.

overrideUserInfoOnSignIn: A boolean value that determines whether to override the user information in the database when signing in. By default, it is set to false, meaning that the user information will not be overridden during sign-in. If you want to update the user information every time they sign in, set this to true.

refreshAccessToken: A custom function to refresh the token. This feature is only supported for built-in social providers (Google, Facebook, GitHub, etc.) and is not currently supported for custom OAuth providers configured through the Generic OAuth Plugin. For built-in providers, you can provide a custom function to refresh the token if needed.

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.

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.
  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=....

concepts: Plugins

URL: /docs/concepts/plugins Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/concepts/plugins.mdx

Learn how to use plugins with Better Auth.


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 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.

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.

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.

<File name="auth-client.ts" />

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 endpoints 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.

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.

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.

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 field. 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)

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.


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.

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: createAuthMiddleware(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 middlewares 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.

const myPlugin = ()=>{
    return {
        id: "my-plugin",
        middlewares: [
            {
                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.

Heres 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.
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.

Heres 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.
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.

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.

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.

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.

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.

import type { BetterAuthClientPlugin } from "better-auth/client";
import type { myPlugin } from "./plugin";

const myPluginClient = ()=> {
    return  {
        id: "my-plugin",
        $InferServerPlugin: {} as ReturnType<typeof myPlugin>,
    } 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.

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<typeof myPlugin>,
    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.

import { atom } from "nanostores";
import type { BetterAuthClientPlugin } from "better-auth/client";

const myPluginClient = {
    id: "my-plugin",
    $InferServerPlugin: {} as ReturnType<typeof myPlugin>,
    getAtoms: ($fetch)=>{
        const myAtom = atom<null>()
        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").

import type { BetterAuthClientPlugin } from "better-auth/client";
import type { myPlugin } from "./plugin";

const myPluginClient = {
    id: "my-plugin",
    $InferServerPlugin: {} as ReturnType<typeof myPlugin>,
    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.

concepts: Rate Limit

URL: /docs/concepts/rate-limit Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/concepts/rate-limit.mdx

How to limit the number of requests a user can make to the server in a given time period.


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
Server-side requests made using `auth.api` aren't affected by rate limiting. Rate limits only apply to client-initiated requests.

You can easily customize these settings by passing the rateLimit object to the betterAuth function.

import { betterAuth } from "better-auth";

export const auth = betterAuth({
    rateLimit: {
        window: 10, // time window in seconds
        max: 100, // max requests in the window
    },
})

Rate limiting is disabled in development mode by default. In order to enable it, set enabled to true:

export const auth = betterAuth({
    rateLimit: {
        enabled: true,
        //...other options
    },
})

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

Connecting IP Address

Rate limiting uses the connecting IP address to track the number of requests made by a user. The default header checked is x-forwarded-for, which is commonly used in production environments. If you are using a different header to track the user's IP address, you'll need to specify it.

export const auth = betterAuth({
    //...other options
    advanced: {
        ipAddress: {
          ipAddressHeaders: ["cf-connecting-ip"], // Cloudflare specific header example
      },
    },
    rateLimit: {
        enabled: true,
        window: 60, // time window in seconds
        max: 100, // max requests in the window
    },
})

Rate Limit Window

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.

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,
                }
            }
        },
    },
})

If you like to disable rate limiting for a specific path, you can set it to false or return false from the custom rule function.

import { betterAuth } from "better-auth";

export const auth = betterAuth({
    //...other options
    rateLimit: {
        customRules: {
            "/get-session": false,
        },
    },
})

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

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.

npx @better-auth/cli migrate

Using Secondary Storage

If a Secondary Storage has been configured you can use that to store rate limit data.

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.

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

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

import { authClient } from "./auth-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

<DatabaseTable fields={[ { name: "id", type: "string", description: "Database ID", isPrimaryKey: true }, { name: "key", type: "string", description: "Unique identifier for each rate limit key", }, { name: "count", type: "integer", description: "Time window in seconds" }, { name: "lastRequest", type: "bigint", description: "Max requests in the window" }]} />

concepts: Session Management

URL: /docs/concepts/session-management Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/concepts/session-management.mdx

Better Auth session management.


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.

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)
    }
})

Disable Session Refresh

You can disable session refresh so that the session is not updated regardless of the updateAge option.

import { betterAuth } from "better-auth"

export const auth = betterAuth({
    //... other config options
    session: {
        disableSessionRefresh: true
    }
})

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:

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:

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.

import { authClient } from "@/lib/client"

const { data: session } = await authClient.getSession()

To learn how to customize the session response check the Customizing Session Response section.

Use Session

The useSession action provides a reactive way to access the current session.

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.

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.

await authClient.revokeSession({
    token: "session-token"
})

Revoke Other Sessions

To revoke all other sessions except the current session, you can use the revokeOtherSessions function.

await authClient.revokeOtherSessions()

Revoke All Sessions

To revoke all sessions, you can use the revokeSessions function.

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.

await authClient.changePassword({
    newPassword: newPassword,
    currentPassword: currentPassword,
    revokeOtherSessions: true,
})

Session Caching

Calling your database every time useSession or getSession invoked isnt ideal, especially if sessions dont 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:

import { betterAuth } from "better-auth"

export const auth = 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 this will force the server to fetch the session from the database and also refresh the cookie cache.

const session = await authClient.getSession({ query: {
    disableCookieCache: true
}})

or on the server

await 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.

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

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<typeof auth>()],
});

const { data } = authClient.useSession();
const { data: sessionData } = await authClient.getSession();
// data.roles
// data.user.newField

Caveats on Customizing Session Response

  1. 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.

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 }, ctx) => {
            // 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]
    ]
})
  1. When your server and client code are in separate projects or repositories, and you cannot import the auth instance as a type reference, type inference for custom session fields will not work on the client side.
  2. 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.

Mutating the list-device-sessions endpoint The /multi-session/list-device-sessions endpoint from the multi-session plugin is used to list the devices that the user is signed into.

You can mutate the response of this endpoint by passing the shouldMutateListDeviceSessionsEndpoint option to the customSession plugin.

By default, we do not mutate the response of this endpoint.

import { customSession } from "better-auth/plugins";

export const auth = betterAuth({
    plugins: [
        customSession(async ({ user, session }, ctx) => {
            return {
                user,
                session
            }
        }, {}, { shouldMutateListDeviceSessionsEndpoint: true }), // [!code highlight]
    ],
});

concepts: TypeScript

URL: /docs/concepts/typescript Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/concepts/typescript.mdx

Better Auth TypeScript integration.


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:

{
  "compilerOptions": {
    "strict": true
  }
}

if you can't set strict to true, you can enable strictNullChecks:

{
  "compilerOptions": {
    "strictNullChecks": true,
  }
}
If you're running into issues with TypeScript inference exceeding maximum length the compiler will serialize, then please make sure you're following the instructions above, as well as ensuring that both `declaration` and `composite` are not enabled.

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.

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.

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.

import { betterAuth } from "better-auth"
import Database from "better-sqlite3"

export const auth = betterAuth({
    database: new Database("database.db"),
    user: {
       additionalFields: {
          role: {
              type: "string",
              input: false
            } 
        }
    }
   
})

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.

The input property

The input property in an additional field configuration determines whether the field should be included in the user input. This property defaults to true, meaning the field will be part of the user input during operations like registration.

To prevent a field from being part of the user input, you must explicitly set input: false:

additionalFields: {
    role: {
        type: "string",
        input: false
    }
}

When input is set to false, the field will be excluded from user input, preventing users from passing a value for it.

By default, additional fields are included in the user input, which can lead to security vulnerabilities if not handled carefully. For fields that should not be set by the user, like a role, it is crucial to set input: false in the configuration.

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.

import { inferAdditionalFields } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/react";
import type { auth } from "./auth";

export const authClient = createAuthClient({
  plugins: [inferAdditionalFields<typeof auth>()],
});
  1. 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.

import { inferAdditionalFields } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [inferAdditionalFields({
      user: {
        role: {
          type: "string"
        }
      }
  })],
});

concepts: User & Accounts

URL: /docs/concepts/users-accounts Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/concepts/users-accounts.mdx

User and account management.


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.

The user table stores the authentication data of the user Click here to view the schema.

The user table can be extended using additional fields 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:

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:

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.

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 users email. The user must verify their current email before changing it.

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

A user's password 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 one of the following approaches:

Client Side

const { data, error } = await authClient.changePassword({
    newPassword: newpassword1234,
    currentPassword: oldpassword1234,
    revokeOtherSessions, // required
});

Server Side

const data = await auth.api.changePassword({
    body: {
        newPassword: newpassword1234,
        currentPassword: oldpassword1234,
        revokeOtherSessions, // required
    },
    // This endpoint requires session cookies.
    headers: await headers()
});

Type Definition

type changePassword = {
    /**
     * The new password to set 
     */
    newPassword: string = "newpassword1234"
    /**
     * The current user password 
     */
    currentPassword: string = "oldpassword1234"
    /**
     * When set to true, all other active sessions for this user will be invalidated
     */
    revokeOtherSessions?: boolean = true

}

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.

await auth.api.setPassword({
    body: { newPassword: "password" },
    headers: // headers containing the user's session token
});

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

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, youll likely want to confirm the users intent before deleting their account. A common approach is to send a verification email. Better Auth provides a sendDeleteAccountVerification utility for this purpose. This is especially needed if you have OAuth setup and want them to be able to delete their account without forcing them to login again for a fresh session.

Heres how you can set it up:

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.
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 theyre attempting to delete. If they arent 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.

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.

await authClient.deleteUser({
    password: "password"
});
  1. 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. It is recommended not to disable this check if you are not using email verification for deleting the account.
await authClient.deleteUser();
  1. Enabled email verification (needed for OAuth users)

As OAuth users don't have a password, we need to send a verification email to confirm the user's intent to delete their account. If you have already added the sendDeleteAccountVerification callback, you can just call the deleteUser method without providing any other information.

await authClient.deleteUser();
  1. If you have a custom delete account page and sent that url via the sendDeleteAccountVerification callback. Then you need to call the deleteUser method with the token to complete the deletion.
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.

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.

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.

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.

The account table stores the authentication data of the user Click here to view the schema

List User Accounts

To list user accounts you can use client.user.listAccounts method. Which will return all accounts associated with a user.

const accounts = await authClient.listAccounts();

Token Encryption

Better Auth doesnt encrypt tokens by default and thats intentional. We want you to have full control over how encryption and decryption are handled, rather than baking in behavior that could be confusing or limiting. If you need to store encrypted tokens (like accessToken or refreshToken), you can use databaseHooks to encrypt them before theyre saved to your database.

export const auth = betterAuth({
    databaseHooks: {
        account: {
            create: {
                before(account, context) {
                    const withEncryptedTokens = { ...account };
                    if (account.accessToken) {
                        const encryptedAccessToken = encrypt(account.accessToken)  // [!code highlight]
                        withEncryptedTokens.accessToken = encryptedAccessToken;
                    }
                    if (account.refreshToken) {
                        const encryptedRefreshToken = encrypt(account.refreshToken); // [!code highlight]
                        withEncryptedTokens.refreshToken = encryptedRefreshToken;
                    }
                    return {
                        data: withEncryptedTokens
                    }
                },
            }
        }
    }
})

Then whenever you retrieve back the account make sure to decrypt the tokens before using them.

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.

export const auth = 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 doesnt confirm the email verification status. Use this with caution as it may increase the risk of account takeover.

export const auth = 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 linkSocial method on the client to link a social provider to the user's account.

    await authClient.linkSocial({
        provider: "google", // Provider to link
        callbackURL: "/callback" // Callback URL after linking completes
    });
    

    You can also request specific scopes when linking a social account, which can be different from the scopes used during the initial authentication:

    await authClient.linkSocial({
        provider: "google",
        callbackURL: "/callback",
        scopes: ["https://www.googleapis.com/auth/drive.readonly"] // Request additional scopes
    });
    

    You can also link accounts using ID tokens directly, without redirecting to the provider's OAuth flow:

    await authClient.linkSocial({
        provider: "google",
        idToken: {
            token: "id_token_from_provider",
            nonce: "nonce_used_for_token", // Optional
            accessToken: "access_token", // Optional, may be required by some providers
            refreshToken: "refresh_token" // Optional
        }
    });
    

    This is useful when you already have valid tokens from the provider, for example:

    • After signing in with a native SDK
    • When using a mobile app that handles authentication
    • When implementing custom OAuth flows

    The ID token must be valid and the provider must support ID token verification.

    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.

    export const auth = betterAuth({
        account: {
            accountLinking: {
                allowDifferentEmails: true
            }
        },
    });
    

    If you want the newly linked accounts to update the user information, you need to enable this in the account linking settings.

    export const auth = betterAuth({
        account: {
            accountLinking: {
                updateUserInfoOnLink: 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.

    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.

await authClient.unlinkAccount({
    providerId: "google"
});

// Unlink a specific account
await authClient.unlinkAccount({
    providerId: "google",
    accountId: "123"
});

If the account doesn't exist, it will throw an error. Additionally, if the user only has one account, unlinking will be prevented to stop account lockout (unless allowUnlinkingAll is set to true).

export const auth = betterAuth({
    account: {
        accountLinking: {
            allowUnlinkingAll: true
        }
    },
});

examples: Astro Example

URL: /docs/examples/astro Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/examples/astro.mdx

Better Auth Astro example.


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