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

{$session.data?.user.email}

```
```ts title="user.svelte" import { authClient } from "~/lib/auth-client"; //import the auth client authClient.useSession.subscribe((value)=>{ //do something with the session // }) ``` ```tsx title="user.tsx" import { authClient } from "~/lib/auth-client"; // [!code highlight] export default function Home() { const session = authClient.useSession() // [!code highlight] return (
{JSON.stringify(session(), null, 2)}
); } ```
#### Get Session If you prefer not to use the hook, you can use the `getSession` method provided by the client. ```ts title="user.tsx" import { authClient } from "@/lib/auth-client" // import the auth client // [!code highlight] const { data: session, error } = await authClient.getSession() ``` You can also use it with client-side data-fetching libraries like [TanStack Query](https://tanstack.com/query/latest). ### Server Side The server provides a `session` object that you can use to access the session data. It requires request headers object to be passed to the `getSession` method. **Example: Using some popular frameworks** ```ts title="server.ts" import { auth } from "./auth"; // path to your Better Auth server instance import { headers } from "next/headers"; const session = await auth.api.getSession({ headers: await headers() // you need to pass the headers object. }) ``` ```ts title="route.ts" import { auth } from "lib/auth"; // path to your Better Auth server instance export async function loader({ request }: LoaderFunctionArgs) { const session = await auth.api.getSession({ headers: request.headers }) return json({ session }) } ``` ```astro title="index.astro" --- import { auth } from "./auth"; const session = await auth.api.getSession({ headers: Astro.request.headers, }); --- ``` ```ts title="+page.ts" import { auth } from "./auth"; export async function load({ request }) { const session = await auth.api.getSession({ headers: request.headers }) return { props: { session } } } ``` ```ts title="index.ts" import { auth } from "./auth"; const app = new Hono(); app.get("/path", async (c) => { const session = await auth.api.getSession({ headers: c.req.raw.headers }) }); ``` ```ts title="server/session.ts" import { auth } from "~/utils/auth"; export default defineEventHandler((event) => { const session = await auth.api.getSession({ headers: event.headers, }) }); ``` ```ts title="app/routes/api/index.ts" import { auth } from "./auth"; import { createAPIFileRoute } from "@tanstack/start/api"; export const APIRoute = createAPIFileRoute("/api/$")({ GET: async ({ request }) => { const session = await auth.api.getSession({ headers: request.headers }) }, }); ``` For more details check [session-management](/docs/concepts/session-management) documentation. ## Using Plugins One of the unique features of Better Auth is a plugins ecosystem. It allows you to add complex auth related functionality with small lines of code. Below is an example of how to add two factor authentication using two factor plugin. ### Server Configuration To add a plugin, you need to import the plugin and pass it to the `plugins` option of the auth instance. For example, to add two factor authentication, you can use the following code: ```ts title="auth.ts" import { betterAuth } from "better-auth" import { twoFactor } from "better-auth/plugins" // [!code highlight] export const auth = betterAuth({ //...rest of the options plugins: [ // [!code highlight] twoFactor() // [!code highlight] ] // [!code highlight] }) ``` now two factor related routes and method will be available on the server. ### Migrate Database After adding the plugin, you'll need to add the required tables to your database. You can do this by running the `migrate` command, or by using the `generate` command to create the schema and handle the migration manually. generating the schema: ```bash title="terminal" npx @better-auth/cli generate ``` using the `migrate` command: ```bash title="terminal" npx @better-auth/cli migrate ``` If you prefer adding the schema manually, you can check the schema required on the [two factor plugin](/docs/plugins/2fa#schema) documentation. ### Client Configuration Once we're done with the server, we need to add the plugin to the client. To do this, you need to import the plugin and pass it to the `plugins` option of the auth client. For example, to add two factor authentication, you can use the following code: ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client"; import { twoFactorClient } from "better-auth/client/plugins"; // [!code highlight] const authClient = createAuthClient({ plugins: [ // [!code highlight] twoFactorClient({ // [!code highlight] twoFactorPage: "/two-factor" // the page to redirect if a user needs 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: npm pnpm yarn bun ```bash npm install better-auth ``` ```bash pnpm add better-auth ``` ```bash yarn add better-auth ``` ```bash bun add better-auth ``` If you're using a separate client and server setup, make sure to install Better Auth in both parts of your project. ### Set Environment Variables Create a `.env` file in the root of your project and add the following environment variables: 1. **Secret Key** Random value used by the library for encryption and generating hashes. **You can generate one using the button below** or you can use something like openssl. ```txt title=".env" BETTER_AUTH_SECRET= ``` 2. **Set Base URL** ```txt title=".env" BETTER_AUTH_URL=http://localhost:3000 # Base URL of your app ``` ### Create A Better Auth Instance Create a file named `auth.ts` in one of these locations: * Project root * `lib/` folder * `utils/` folder You can also nest any of these folders under `src/`, `app/` or `server/` folder. (e.g. `src/lib/auth.ts`, `app/lib/auth.ts`). And in this file, import Better Auth and create your auth instance. Make sure to export the auth instance with the variable name `auth` or as a `default` export. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ //... }); ``` ### Configure Database Better Auth requires a database to store user data. You can easily configure Better Auth to use SQLite, PostgreSQL, or MySQL, and more! ```ts title="auth.ts" import { betterAuth } from "better-auth"; import Database from "better-sqlite3"; export const auth = betterAuth({ database: new Database("./sqlite.db"), }) ``` ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { Pool } from "pg"; export const auth = betterAuth({ database: new Pool({ // connection options }), }) ``` ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { createPool } from "mysql2/promise"; export const auth = betterAuth({ database: createPool({ // connection options }), }) ``` Alternatively, if you prefer to use an ORM, you can use one of the built-in adapters. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { db } from "@/db"; // your drizzle instance export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", // or "mysql", "sqlite" }), }); ``` ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { 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 }), }); ``` ```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), }); ``` 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. ### Create Database Tables Better Auth includes a CLI tool to help manage the schema required by the library. * **Generate**: This command generates an ORM schema or SQL migration file. If you're using Kysely, you can apply the migration directly with `migrate` command below. Use `generate` only if you plan to apply the migration manually. ```bash title="Terminal" npx @better-auth/cli generate ``` * **Migrate**: This command creates the required tables directly in the database. (Available only for the built-in Kysely adapter) ```bash title="Terminal" npx @better-auth/cli migrate ``` see the [CLI documentation](/docs/concepts/cli) for more information. If you instead want to create the schema manually, you can find the core schema required in the [database section](/docs/concepts/database#core-schema). ### Authentication Methods Configure the authentication methods you want to use. Better Auth comes with built-in support for email/password, and social sign-on providers. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ //...other options emailAndPassword: { // [!code highlight] enabled: true, // [!code highlight] }, // [!code highlight] socialProviders: { // [!code highlight] github: { // [!code highlight] clientId: process.env.GITHUB_CLIENT_ID as string, // [!code highlight] clientSecret: process.env.GITHUB_CLIENT_SECRET as string, // [!code highlight] }, // [!code highlight] }, // [!code highlight] }); ``` You can use even more authentication methods like [passkey](/docs/plugins/passkey), [username](/docs/plugins/username), [magic link](/docs/plugins/magic-link) and more through plugins. ### Mount Handler To handle API requests, you need to set up a route handler on your server. Create a new file or route in your framework's designated catch-all route handler. This route should handle requests for the path `/api/auth/*` (unless you've configured a different base path). Better Auth supports any backend framework with standard Request and Response objects and offers helper functions for popular frameworks. ```ts title="/app/api/auth/[...all]/route.ts" import { auth } from "@/lib/auth"; // path to your auth file import { toNextJsHandler } from "better-auth/next-js"; export const { POST, GET } = toNextJsHandler(auth); ``` ```ts title="/server/api/auth/[...all].ts" import { auth } from "~/utils/auth"; // path to your auth file export default defineEventHandler((event) => { return auth.handler(toWebRequest(event)); }); ``` ```ts title="hooks.server.ts" import { auth } from "$lib/auth"; // path to your auth file import { svelteKitHandler } from "better-auth/svelte-kit"; import { building } from '$app/environment' export async function handle({ event, resolve }) { return svelteKitHandler({ event, resolve, auth, building }); } ``` ```ts title="/app/routes/api.auth.$.ts" import { auth } from '~/lib/auth.server' // Adjust the path as necessary import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node" export async function loader({ request }: LoaderFunctionArgs) { return auth.handler(request) } export async function action({ request }: ActionFunctionArgs) { return auth.handler(request) } ``` ```ts title="/routes/api/auth/*all.ts" import { auth } from "~/lib/auth"; // path to your auth file import { toSolidStartHandler } from "better-auth/solid-start"; export const { GET, POST } = toSolidStartHandler(auth); ``` ```ts title="src/index.ts" import { Hono } from "hono"; import { auth } from "./auth"; // path to your auth file import { serve } from "@hono/node-server"; import { cors } from "hono/cors"; const app = new Hono(); app.on(["POST", "GET"], "/api/auth/*", (c) => auth.handler(c.req.raw)); serve(app); ``` 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.* ```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. ```ts title="/pages/api/auth/[...all].ts" import type { APIRoute } from "astro"; import { auth } from "@/auth"; // path to your auth file export const GET: APIRoute = async (ctx) => { return auth.handler(ctx.request); }; export const POST: APIRoute = async (ctx) => { return auth.handler(ctx.request); }; ``` ```ts import { Elysia, Context } from "elysia"; import { auth } from "./auth"; const betterAuthView = (context: Context) => { const BETTER_AUTH_ACCEPT_METHODS = ["POST", "GET"] // validate request method if(BETTER_AUTH_ACCEPT_METHODS.includes(context.request.method)) { return auth.handler(context.request); } else { context.error(405) } } const app = new Elysia().all("/api/auth/*", betterAuthView).listen(3000); console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ); ``` ```ts title="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) }, }); ``` ```ts title="app/api/auth/[...all]+api.ts" import { auth } from '@/lib/server/auth'; // path to your auth file const handler = auth.handler; export { handler as GET, handler as POST }; ``` ### Create Client Instance The client-side library helps you interact with the auth server. Better Auth comes with a client for all the popular web frameworks, including vanilla JavaScript. 1. Import `createAuthClient` from the package for your framework (e.g., "better-auth/react" for React). 2. Call the function to create your client. 3. Pass the base URL of your auth server. (If the auth server is running on the same domain as your client, you can skip this step.) If you're using a different base path other than `/api/auth` make sure to pass the whole URL including the path. (e.g. `http://localhost:3000/custom-path/auth`) ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/client" export const authClient = createAuthClient({ /** The base URL of the server (optional if you're using the same domain) */ // [!code highlight] baseURL: "http://localhost:3000" // [!code highlight] }) ``` ```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] }) ``` ```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] }) ``` ```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] }) ``` ```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] }) ``` Tip: You can also export specific methods if you prefer: ```ts export const { signIn, signUp, useSession } = createAuthClient() ``` ### 🎉 That's it! That's it! You're now ready to use better-auth in your application. Continue to [basic usage](/docs/basic-usage) to learn how to use the auth instance to sign in users. # 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](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](https://github.com/get-convex/better-auth) | [Convex Database](https://www.convex.dev/) | [erquhart](https://github.com/erquhart) | | [surreal-better-auth](https://github.com/oskar-gmerek/surreal-better-auth) | [SurrealDB](https://surrealdb.com/) | Oskar Gmerek | | [surrealdb-better-auth](https://github.com/Necmttn/surrealdb-better-auth) | [Surreal Database](https://surrealdb.com/) | [Necmttn](https://github.com/Necmttn) | | [better-auth-surrealdb](https://github.com/msanchezdev/better-auth-surrealdb) | [Surreal Database](https://surrealdb.com/) | [msanchezdev](https://github.com/msanchezdev) | | [payload-better-auth](https://github.com/ForrestDevs/payload-better-auth/tree/main/packages/db-adapter) | [Payload CMS](https://payloadcms.com/) | [forrestdevs](https://github.com/forrestdevs) | | [@ronin/better-auth](https://github.com/ronin-co/better-auth) | [RONIN](https://ronin.co) | [ronin-co](https://github.com/ronin-co) | | [better-auth-instantdb](https://github.com/daveyplate/better-auth-instantdb) | [InstantDB](https://www.instantdb.com/) | [daveycodez](https://github.com/daveycodez) | | [@nerdfolio/remult-better-auth](https://github.com/nerdfolio/remult-better-auth) | [Remult](https://remult.dev/) | [Tai Vo](https://github.com/taivo) | # 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](https://orm.drizzle.team/). ## Example Usage Make sure you have Drizzle installed and configured. Then, you can use the Drizzle adapter to connect to your database. ```ts title="auth.ts" 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](/docs/concepts/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: ```bash title="Schema Generation" npx @better-auth/cli@latest generate ``` To generate and apply the migration, run the following commands: ```bash title="Schema Migration" 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. ```ts import { betterAuth } from "better-auth"; import { db } from "./drizzle"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { schema } from "./schema"; export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "sqlite", // or "pg" or "mysql" schema: { ...schema, user: schema.users, }, }), }); ``` If all your tables are using plural form, you can just pass the `usePlural` option: ```ts 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](https://www.mongodb.com/). ## Example Usage Make sure you have MongoDB installed and configured. Then, you can use the mongodb adapter. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { MongoClient } from "mongodb"; import { mongodbAdapter } from "better-auth/adapters/mongodb"; const client = new MongoClient("mongodb://localhost:27017/database"); const db = client.db(); export const auth = betterAuth({ database: mongodbAdapter(db, { // Optional: if you don't provide a client, database transactions won't be enabled. client }), }); ``` ## 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](https://en.wikipedia.org/wiki/Microsoft_SQL_Server). ## Example Usage Make sure you have MS SQL installed and configured. Then, you can connect it straight into Better Auth. ```ts title="auth.ts" 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](/docs/concepts/cli) allows you to generate or migrate your database schema based on your Better Auth configuration and plugins.

MS SQL Schema Generation

MS SQL Schema Migration

✅ Supported ✅ Supported
```bash title="Schema Generation" npx @better-auth/cli@latest generate ``` ```bash title="Schema Migration" npx @better-auth/cli@latest migrate ``` ## Additional Information MS SQL is supported under the hood via the [Kysely](https://kysely.dev/) 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](https://www.mysql.com/). ## Example Usage Make sure you have MySQL installed and configured. Then, you can connect it straight into Better Auth. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { createPool } from "mysql2/promise"; export const auth = betterAuth({ database: createPool({ host: "localhost", user: "root", password: "password", database: "database", }), }); ``` 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](/docs/concepts/cli) allows you to generate or migrate your database schema based on your Better Auth configuration and plugins.

MySQL Schema Generation

MySQL Schema Migration

✅ Supported ✅ Supported
```bash title="Schema Generation" npx @better-auth/cli@latest generate ``` ```bash title="Schema Migration" npx @better-auth/cli@latest migrate ``` ## Additional Information MySQL is supported under the hood via the [Kysely](https://kysely.dev/) 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 * [MySQL](/docs/adapters/mysql) * [SQLite](/docs/adapters/sqlite) * [PostgreSQL](/docs/adapters/postgresql) * [MS SQL](/docs/adapters/mssql) ## Kysely Organization Dialects * [Postgres.js](https://github.com/kysely-org/kysely-postgres-js) * [SingleStore Data API](https://github.com/kysely-org/kysely-singlestore) ## Kysely Community dialects * [PlanetScale Serverless Driver](https://github.com/depot/kysely-planetscale) * [Cloudflare D1](https://github.com/aidenwallis/kysely-d1) * [AWS RDS Data API](https://github.com/serverless-stack/kysely-data-api) * [SurrealDB](https://github.com/igalklebanov/kysely-surrealdb) * [Neon](https://github.com/seveibar/kysely-neon) * [Xata](https://github.com/xataio/client-ts/tree/main/packages/plugin-client-kysely) * [AWS S3 Select](https://github.com/igalklebanov/kysely-s3-select) * [libSQL/sqld](https://github.com/libsql/kysely-libsql) * [Fetch driver](https://github.com/andersgee/kysely-fetch-driver) * [SQLite WASM](https://github.com/DallasHoff/sqlocal) * [Deno SQLite](https://gitlab.com/soapbox-pub/kysely-deno-sqlite) * [TiDB Cloud Serverless Driver](https://github.com/tidbcloud/kysely) * [Capacitor SQLite Kysely](https://github.com/DawidWetzler/capacitor-sqlite-kysely) * [BigQuery](https://github.com/maktouch/kysely-bigquery) * [Clickhouse](https://github.com/founderpathcom/kysely-clickhouse) * [PGLite](https://github.com/czeidler/kysely-pglite-dialect) 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](https://www.postgresql.org/). ## Example Usage Make sure you have PostgreSQL installed and configured. Then, you can connect it straight into Better Auth. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { Pool } from "pg"; export const auth = betterAuth({ database: new Pool({ connectionString: "postgres://user:password@localhost:5432/database", }), }); ``` 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](/docs/concepts/cli) allows you to generate or migrate your database schema based on your Better Auth configuration and plugins.

PostgreSQL Schema Generation

PostgreSQL Schema Migration

✅ Supported ✅ Supported
```bash title="Schema Generation" npx @better-auth/cli@latest generate ``` ```bash title="Schema Migration" npx @better-auth/cli@latest migrate ``` ## Additional Information PostgreSQL is supported under the hood via the [Kysely](https://kysely.dev/) 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](https://www.prisma.io/). ## Example Usage Make sure you have Prisma installed and configured. Then, you can use the Prisma adapter to connect to your database. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { prismaAdapter } from "better-auth/adapters/prisma"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); export const auth = betterAuth({ database: prismaAdapter(prisma, { provider: "sqlite", }), }); ``` 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](/docs/concepts/cli) allows you to generate or migrate your database schema based on your Better Auth configuration and plugins.

Prisma Schema Generation

Prisma Schema Migration

✅ Supported ❌ Not Supported
```bash title="Schema Generation" 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.](https://www.sqlite.org/) ## Example Usage Better Auth supports multiple SQLite drivers. Choose the one that best fits your environment: ### Better-SQLite3 (Recommended) The most popular and stable SQLite driver for Node.js: ```ts title="auth.ts" 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](https://nodejs.org/api/sqlite.html) module: ```ts title="auth.ts" 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: ```bash node your-app.js ``` ### Bun Built-in SQLite You can also use the built-in [SQLite](https://bun.com/docs/api/sqlite) module in Bun, which is similar to the Node.js version: ```ts title="auth.ts" 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](/docs/concepts/cli) allows you to generate or migrate your database schema based on your Better Auth configuration and plugins.

SQLite Schema Generation

SQLite Schema Migration

✅ Supported ✅ Supported
```bash title="Schema Generation" npx @better-auth/cli@latest generate ``` ```bash title="Schema Migration" npx @better-auth/cli@latest migrate ``` ## Additional Information SQLite is supported under the hood via the [Kysely](https://kysely.dev/) 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`. ```ts title="auth-client.ts" / import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "apple" }) } ``` ### Sign In with Apple With ID Token To sign in with Apple using the ID Token, you can use the `signIn.social` function to pass the ID Token. This is useful when you have the ID Token from Apple on the client-side and want to use it to sign in on the server. If ID token is provided no redirection will happen, and the user will be signed in directly. ```ts title="auth-client.ts" await authClient.signIn.social({ provider: "apple", idToken: { token: // Apple ID Token, nonce: // Nonce (optional) accessToken: // Access Token (optional) } }) ``` ## 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 * 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/). 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" }) } ``` 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/). # 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`). * **User Pool is required** for Cognito authentication. * Make sure the callback URL matches exactly what you configure in Cognito. ### 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 user’s email * `phone`: Access to user’s 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. 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). # 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](https://developers.dropbox.com/oauth-guide). ### Configure the provider To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { dropbox: { // [!code highlight] clientId: process.env.DROPBOX_CLIENT_ID as string, // [!code highlight] clientSecret: process.env.DROPBOX_CLIENT_SECRET as string, // [!code highlight] }, // [!code highlight] }, }) ``` ### Sign In with Dropbox To sign in with Dropbox, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `dropbox`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "dropbox" }) } ``` # 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. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ emailAndPassword: { // [!code highlight] enabled: true, // [!code highlight] }, // [!code highlight] }); ``` If it's not enabled, it'll not allow you to sign in or sign up with email and password. ## Usage ### Sign Up To sign a user up, you can use the `signUp.email` function provided by the client. ### Client Side ```ts 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 ```ts 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 ```ts 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 ```ts const { data, error } = await authClient.signIn.email({ email: john.doe@example.com, password: password1234, rememberMe, // required callbackURL: https://example.com/callback, // required }); ``` ### Server Side ```ts 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 ```ts 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 ```ts const { data, error } = await authClient.signOut({}); ``` ### Server Side ```ts await auth.api.signOut({ // This endpoint requires session cookies. headers: await headers() }); ``` ### Type Definition ```ts type signOut = { } ``` you can pass `fetchOptions` to redirect onSuccess ```ts title="auth-client.ts" await authClient.signOut({ fetchOptions: { onSuccess: () => { router.push("/login"); // redirect to login page }, }, }); ``` ### Email Verification To enable email verification, you need to pass a function that sends a verification email with a link. The `sendVerificationEmail` function takes a data object with the following properties: * `user`: The user object. * `url`: The URL to send to the user which contains the token. * `token`: A verification token used to complete the email verification. and a `request` object as the second parameter. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { sendEmail } from "./email"; // your email sending function export const auth = betterAuth({ emailVerification: { sendVerificationEmail: async ( { user, url, token }, request) => { await sendEmail({ to: user.email, subject: "Verify your email address", text: `Click the link to verify your email: ${url}`, }); }, }, }); ``` On the client side you can use `sendVerificationEmail` function to send verification link to user. This will trigger the `sendVerificationEmail` function you provided in the `auth` configuration. Once the user clicks on the link in the email, if the token is valid, the user will be redirected to the URL provided in the `callbackURL` parameter. If the token is invalid, the user will be redirected to the URL provided in the `callbackURL` parameter with an error message in the query string `?error=invalid_token`. #### Require Email Verification If you enable require email verification, users must verify their email before they can log in. And every time a user tries to sign in, sendVerificationEmail is called. This only works if you have sendVerificationEmail implemented and if the user is trying to sign in with email and password. ```ts title="auth.ts" export const auth = betterAuth({ emailAndPassword: { requireEmailVerification: true, }, }); ``` If a user tries to sign in without verifying their email, you can handle the error and show a message to the user. ```ts title="auth-client.ts" await authClient.signIn.email( { email: "email@example.com", password: "password", }, { onError: (ctx) => { // Handle the error if (ctx.error.status === 403) { alert("Please verify your email address"); } //you can also show the original error message alert(ctx.error.message); }, } ); ``` #### Triggering manually Email Verification You can trigger the email verification manually by calling the `sendVerificationEmail` function. ```ts await authClient.sendVerificationEmail({ email: "user@email.com", callbackURL: "/", // The redirect URL after verification }); ``` ### 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. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { sendEmail } from "./email"; // your email sending function export const auth = betterAuth({ emailAndPassword: { enabled: true, sendResetPassword: async ({user, url, token}, request) => { await sendEmail({ to: user.email, subject: "Reset your password", text: `Click the link to reset your password: ${url}`, }); }, 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 ```ts const { data, error } = await authClient.requestPasswordReset({ email: john.doe@example.com, redirectTo: https://example.com/reset-password, // required }); ``` ### Server Side ```ts const data = await auth.api.requestPasswordReset({ body: { email: john.doe@example.com, redirectTo: https://example.com/reset-password, // required } }); ``` ### Type Definition ```ts 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. ```ts title="auth-client.ts" const { data, error } = await authClient.resetPassword({ newPassword: "password1234", token, }); ``` ### Client Side ```ts const { data, error } = await authClient.resetPassword({ newPassword: password1234, token, }); ``` ### Server Side ```ts const data = await auth.api.resetPassword({ body: { newPassword: password1234, token, } }); ``` ### Type Definition ```ts 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 ```ts const { data, error } = await authClient.changePassword({ newPassword: newpassword1234, currentPassword: oldpassword1234, revokeOtherSessions, // required }); ``` ### Server Side ```ts const data = await auth.api.changePassword({ body: { newPassword: newpassword1234, currentPassword: oldpassword1234, revokeOtherSessions, // required }, // This endpoint requires session cookies. headers: await headers() }); ``` ### Type Definition ```ts 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. ```ts title="auth.ts" import { betterAuth } from "better-auth" import { scrypt } from "scrypt" export const auth = betterAuth({ //...rest of the options emailAndPassword: { password: { hash: // your custom password hashing function verify: // your custom password verification function } } }) ``` # 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`. Avoid exposing the `clientSecret` in client-side code (e.g., frontend apps) because it’s sensitive information. Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/facebook` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly. ### Configure the provider To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { facebook: { // [!code highlight] clientId: process.env.FACEBOOK_CLIENT_ID as string, // [!code highlight] clientSecret: process.env.FACEBOOK_CLIENT_SECRET as string, // [!code highlight] }, // [!code highlight] }, }) ``` 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. ### Sign In with Facebook To sign in with Facebook, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `facebook`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/auth-client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "facebook" }) } ``` ## Additional Configuration ### Scopes By default, Facebook provides basic user information. If you need additional permissions, you can specify scopes in your auth configuration: ```ts title="auth.ts" export const auth = betterAuth({ socialProviders: { facebook: { clientId: process.env.FACEBOOK_CLIENT_ID as string, clientSecret: process.env.FACEBOOK_CLIENT_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](https://github.com/better-auth/better-auth/issues/1183)]. ```ts title="auth-client.ts" const data = await authClient.signIn.social({ provider: "facebook", idToken: { // [!code highlight] ...(platform === 'ios' ? // [!code highlight] { token: idToken } // [!code highlight] : { token: accessToken, accessToken: accessToken }), // [!code highlight] }, }) ``` For a complete list of available permissions, refer to the [Permissions Reference](https://developers.facebook.com/docs/permissions). # 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 * 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). 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" }) } ``` For more information about Figma's OAuth scopes and API capabilities, refer to the [official Figma API documentation](https://www.figma.com/developers/api). # 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`. ```ts title="auth-client.ts" / import { createAuthClient } from "better-auth/client"; const authClient = createAuthClient(); const signIn = async () => { const data = await authClient.signIn.social({ provider: "google", }); }; ``` ### Sign In with Google With ID Token To sign in with Google using the ID Token, you can use the `signIn.social` function to pass the ID Token. This is useful when you have the ID Token from Google on the client-side and want to use it to sign in on the server. If ID token is provided no redirection will happen, and the user will be signed in directly. ```ts title="auth-client.ts" const data = await authClient.signIn.social({ provider: "google", idToken: { token: // Google ID Token, accessToken: // Google Access Token } }) ``` If you want to use google one tap, you can use the [One Tap Plugin](/docs/plugins/one-tap) guide. ### 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`. ```ts 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. ```tsx title="auth-client.ts" const requestGoogleDriveAccess = async () => { await authClient.linkSocial({ provider: "google", scopes: ["https://www.googleapis.com/auth/drive.file"], }); }; // Example usage in a React component return ( ); ``` 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. ```ts 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"`. ```ts title="auth-client.ts" 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. ```ts title="auth-client.ts" await authClient.signIn.social({ provider: "line", idToken: { token: "", accessToken: "", }, }); ``` ### 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](https://developers.line.biz/en/docs/line-login/login-button/). # 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)](https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow) [Sign In with LinkedIn using OpenID Connect](https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2?context=linkedin%2Fconsumer%2Fcontext) ### Configure the provider To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { linkedin: { // [!code highlight] clientId: process.env.LINKEDIN_CLIENT_ID as string, // [!code highlight] clientSecret: process.env.LINKEDIN_CLIENT_SECRET as string, // [!code highlight] }, // [!code highlight] }, }) ``` ### Sign In with LinkedIn To sign in with LinkedIn, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties: * `provider`: The provider to use. It should be set to `linkedin`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() const signIn = async () => { const data = await authClient.signIn.social({ provider: "linkedin" }) } ``` # 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://.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`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client"; const authClient = createAuthClient(); const signIn = async () => { const data = await authClient.signIn.social({ provider: "microsoft", callbackURL: "/dashboard", // The URL to redirect to after the sign in }); }; ``` # 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. Make sure your Notion integration has the appropriate capabilities enabled. For user authentication, you'll need the "Read user information including email addresses" capability. ### 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`. ```ts title="auth-client.ts" 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. ```ts title="auth-client.ts" const requestNotionAccess = async () => { await authClient.linkSocial({ provider: "notion", // Notion automatically provides access based on integration capabilities }); }; // Example usage in a React component return ; ``` 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](/docs/plugins/generic-oauth) which allows you to use any social provider that implements the OAuth2 protocol or OpenID Connect (OIDC) flows. To use a provider that is not supported out of the box, you can use the [Generic Oauth Plugin](/docs/plugins/generic-oauth). ## Installation ### Add the plugin to your auth config To use the Generic OAuth plugin, add it to your auth config. ```ts title="auth.ts" import { betterAuth } from "better-auth" import { genericOAuth } from "better-auth/plugins" // [!code highlight] export const auth = betterAuth({ // ... other config options plugins: [ genericOAuth({ // [!code highlight] config: [ // [!code highlight] { // [!code highlight] providerId: "provider-id", // [!code highlight] clientId: "test-client-id", // [!code highlight] clientSecret: "test-client-secret", // [!code highlight] discoveryUrl: "https://auth.example.com/.well-known/openid-configuration", // [!code highlight] // ... other config options // [!code highlight] }, // [!code highlight] // Add more providers as needed // [!code highlight] ] // [!code highlight] }) // [!code highlight] ] }) ``` ### Add the client plugin Include the Generic OAuth client plugin in your authentication client instance. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" import { genericOAuthClient } from "better-auth/client/plugins" const authClient = createAuthClient({ plugins: [ genericOAuthClient() ] }) ``` Read more about installation and usage of the Generic Oauth plugin [here](/docs/plugins/generic-oauth#usage). ## Example usage ### Instagram Example ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { genericOAuth } from "better-auth/plugins"; export const auth = betterAuth({ // ... other config options plugins: [ genericOAuth({ config: [ { providerId: "instagram", clientId: process.env.INSTAGRAM_CLIENT_ID as string, clientSecret: process.env.INSTAGRAM_CLIENT_SECRET as string, authorizationUrl: "https://api.instagram.com/oauth/authorize", tokenUrl: "https://api.instagram.com/oauth/access_token", scopes: ["user_profile", "user_media"], }, ], }), ], }); ``` ```ts title="sign-in.ts" const response = await authClient.signIn.oauth2({ providerId: "instagram", callbackURL: "/dashboard", // the path to redirect to after the user is authenticated }); ``` ### Coinbase Example ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { genericOAuth } from "better-auth/plugins"; export const auth = betterAuth({ // ... other config options plugins: [ genericOAuth({ config: [ { providerId: "coinbase", clientId: process.env.COINBASE_CLIENT_ID as string, clientSecret: process.env.COINBASE_CLIENT_SECRET as string, authorizationUrl: "https://www.coinbase.com/oauth/authorize", tokenUrl: "https://api.coinbase.com/oauth/token", scopes: ["wallet:user:read"], // and more... }, ], }), ], }); ``` ```ts title="sign-in.ts" const response = await authClient.signIn.oauth2({ providerId: "coinbase", callbackURL: "/dashboard", // the path to redirect to after the user is authenticated }); ``` # 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 * 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. 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 * 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 ### 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: ```ts title="auth.ts" export const auth = betterAuth({ socialProviders: { reddit: { clientId: process.env.REDDIT_CLIENT_ID as string, clientSecret: process.env.REDDIT_CLIENT_SECRET as string, duration: "permanent", 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](https://www.reddit.com/dev/api/oauth). # 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. The Roblox API does not provide email addresses. As a workaround, the user's `email` field uses the `preferred_username` value instead. ### Configure the provider To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ socialProviders: { 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) * 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 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. ### 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] }, }, }) ``` * 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 ### 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. The default scopes requested are `openid`, `email`, and `profile`. The provider will automatically include the `id` scope for accessing basic user information. # 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 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. ### 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`. ```ts title="auth-client.ts" 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: ```ts title="auth-client.ts" 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: ```ts title="auth.ts" 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: ```ts 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 * 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. 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. * 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. ### 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. 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. ### 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 ``` 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. 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 exists 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. ```ts title="server.ts" import { betterAuth } from "better-auth"; import { headers } from "next/headers"; export const auth = betterAuth({ //... }) // calling get session on the server await auth.api.getSession({ headers: await headers() // some endpoints 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. ```ts title="server.ts" 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 when 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. ```ts 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. ```ts 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. ```ts title="server.ts" const response = await auth.api.signInEmail({ body: { email: "", password: "" }, asResponse: true }) ``` ### Error Handling When you call an API endpoint on the server, it will throw an error if the request fails. You can catch the error and handle it as you see fit. The error instance is an instance of `APIError`. ```ts title="server.ts" import { APIError } from "better-auth/api"; try { await auth.api.signInEmail({ body: { email: "", password: "" } }) } catch (error) { if (error instanceof APIError) { console.log(error.message, error.status) } } ``` # 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. ```bash title="Terminal" npx @better-auth/cli@latest generate ``` ### Options * `--output` - Where to save the generated schema. For Prisma, it will be saved in prisma/schema.prisma. For Drizzle, it goes to schema.ts in your project root. For Kysely, it's an SQL file saved as schema.sql in your project root. * `--config` - The path to your Better Auth config file. By default, the CLI will search for an auth.ts file in **./**, **./utils**, **./lib**, or any of these directories under the `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. ```bash title="Terminal" npx @better-auth/cli@latest migrate ``` ### Options * `--config` - The path to your Better Auth config file. By default, the CLI will search for an auth.ts file in **./**, **./utils**, **./lib**, or any of these directories under the `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. ```bash title="Terminal" npx @better-auth/cli@latest init ``` ### Options * `--name` - The name of your application. (defaults to the `name` property in your `package.json`). * `--framework` - The framework your codebase is using. Currently, the only supported framework is `Next.js`. * `--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. ```bash title="Terminal" 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 ```bash # 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. ```bash title="Terminal" 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 are 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 pnpm yarn bun ```bash npm i better-auth ``` ```bash pnpm add better-auth ``` ```bash yarn add better-auth ``` ```bash bun add better-auth ``` ## Create Client Instance Import `createAuthClient` from the package for your framework (e.g., "better-auth/react" for React). Call the function to create your client. Pass the base URL of your auth server. If the auth server is running on the same domain as your client, you can skip this step. If you're using a different base path other than `/api/auth`, make sure to pass the whole URL, including the path. (e.g., `http://localhost:3000/custom-path/auth`) ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/client" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // The base URL of your auth server // [!code highlight] }) ``` ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/react" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // The base URL of your auth server // [!code highlight] }) ``` ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/vue" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // The base URL of your auth server // [!code highlight] }) ``` ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/svelte" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // The base URL of your auth server // [!code highlight] }) ``` ```ts title="lib/auth-client.ts" import { createAuthClient } from "better-auth/solid" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" // The base URL of your auth server // [!code highlight] }) ``` ## Usage Once you've created your client instance, you can use the client to interact with the Better Auth server. The client provides a set of functions by default and they can be extended with plugins. **Example: Sign In** ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() await authClient.signIn.email({ email: "test@user.com", password: "password1234" }) ``` ### Hooks In addition to the standard methods, the client provides hooks to easily access different reactive data. Every hook is available in the root object of the client and they all start with `use`. **Example: useSession** ```tsx title="user.tsx" //make sure you're using the react client import { createAuthClient } from "better-auth/react" const { useSession } = createAuthClient() // [!code highlight] export function User() { const { data: session, isPending, //loading state error, //error object refetch //refetch the session } = useSession() return ( //... ) } ``` ```vue title="user.vue" ``` ```svelte title="user.svelte"
{#if $session}

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

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

{:else} {/if}
```
```tsx title="user.tsx" import { client } from "~/lib/client"; import { Show } from 'solid-js'; export default function Home() { const session = client.useSession() return ( Log in} > ); } ```
### Fetch Options The client uses a library called [better fetch](https://better-fetch.vercel.app) to make requests to the server. Better fetch is a wrapper around the native fetch API that provides a more convenient way to make requests. It's created by the same team behind Better Auth and is designed to work seamlessly with it. You can pass any default fetch options to the client by passing `fetchOptions` object to the `createAuthClient`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient({ fetchOptions: { //any better-fetch options }, }) ``` You can also pass fetch options to most of the client functions. Either as the second argument or as a property in the object. ```ts title="auth-client.ts" await authClient.signIn.email({ email: "email@email.com", password: "password1234", }, { onSuccess(ctx) { // } }) //or await authClient.signIn.email({ email: "email@email.com", password: "password1234", fetchOptions: { onSuccess(ctx) { // } }, }) ``` ### Handling Errors Most of the client functions return a response object with the following properties: * `data`: The response data. * `error`: The error object if there was an error. The error object contains the following properties: * `message`: The error message. (e.g., "Invalid email or password") * `status`: The HTTP status code. * `statusText`: The HTTP status text. ```ts title="auth-client.ts" const { data, error } = await authClient.signIn.email({ email: "email@email.com", password: "password1234" }) if (error) { //handle error } ``` If the action accepts a `fetchOptions` option, you can pass an `onError` callback to handle errors. ```ts title="auth-client.ts" await authClient.signIn.email({ email: "email@email.com", password: "password1234", }, { onError(ctx) { //handle error } }) //or await authClient.signIn.email({ email: "email@email.com", password: "password1234", fetchOptions: { onError(ctx) { //handle error } } }) ``` Hooks like `useSession` also return an error object if there was an error fetching the session. On top of that, they also return an `isPending` property to indicate if the request is still pending. ```ts title="auth-client.ts" const { data, error, isPending } = useSession() if (error) { //handle error } ``` #### Error Codes The client instance contains $ERROR\_CODES object that contains all the error codes returned by the server. You can use this to handle error translations or custom error messages. ```ts title="auth-client.ts" const authClient = createAuthClient(); type ErrorTypes = Partial< Record< keyof typeof 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** ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" import { magicLinkClient } from "better-auth/client/plugins" const authClient = createAuthClient({ plugins: [ magicLinkClient() ] }) ``` once you've added the plugin, you can use the new functions provided by the plugin. ```ts title="auth-client.ts" await authClient.signIn.magicLink({ email: "test@email.com" }) ``` # 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. ### Cookie Prefix By default, Better Auth cookies follow the format `${prefix}.${cookie_name}`. The default prefix is "better-auth". You can change the prefix by setting `cookiePrefix` in the `advanced` object of the auth options. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ advanced: { cookiePrefix: "my-app" } }) ``` ### Custom Cookies All cookies are `httpOnly` and `secure` when 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 flag when `rememberMe` is disabled Plugins may also use cookies to store data. For example, the Two Factor Authentication plugin uses the `two_factor` cookie to store the two-factor authentication state. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ advanced: { cookies: { session_token: { name: "custom_session_token", attributes: { // Set custom cookie attributes } }, } } }) ``` ### Cross Subdomain Cookies Sometimes you may need to share cookies across subdomains. For example, if you authenticate on `auth.example.com`, you may also 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, follow these guidelines: 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`) ```ts title="auth.ts" 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. ```ts title="auth.ts" 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](/docs/adapters/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. ```bash npx @better-auth/cli migrate ``` ### Generating Schema Better Auth also provides a `generate` command to generate the schema required by Better Auth. The `generate` command creates the schema required by Better Auth. If you're using a database adapter like Prisma or Drizzle, this command will generate the right schema for your ORM. If you're using the built-in Kysely adapter, it will generate an SQL file you can run directly on your database. ```bash npx @better-auth/cli generate ``` See the [CLI](/docs/concepts/cli) documentation for more information on the CLI. If you prefer adding tables manually, you can do that as well. The core schema required by Better Auth is described below and you can find additional schema required by plugins in the plugin documentation. ## Secondary Storage Secondary storage in Better Auth allows you to use key-value stores for managing session data, rate limiting counters, etc. This can be useful when you want to offload the storage of this intensive records to a high performance storage or even RAM. ### Implementation To use secondary storage, implement the `SecondaryStorage` interface: ```typescript interface SecondaryStorage { get: (key: string) => Promise; set: (key: string, value: string, ttl?: number) => Promise; delete: (key: string) => Promise; } ``` Then, provide your implementation to the `betterAuth` function: ```typescript betterAuth({ // ... other options secondaryStorage: { // Your implementation here }, }); ``` **Example: Redis Implementation** Here's a basic example using Redis: ```typescript import { createClient } from "redis"; import { betterAuth } from "better-auth"; const redis = createClient(); await redis.connect(); export const auth = betterAuth({ // ... other options secondaryStorage: { get: async (key) => { 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` ### Session Table Name: `session` ### Account Table Name: `account` ### Verification Table Name: `verification` ## Custom Tables Better Auth allows you to customize the table names and column names for the core schema. You can also extend the core schema by adding additional fields to the user and session tables. ### Custom Table Names You can customize the table names and column names for the core schema by using the `modelName` and `fields` properties in your auth config: ```ts title="auth.ts" export const auth = betterAuth({ user: { modelName: "users", fields: { name: "full_name", email: "email_address", }, }, session: { modelName: "user_sessions", fields: { userId: "user_id", }, }, }); ``` Type inference in your code will still use the original field names (e.g., `user.name`, not `user.full_name`). To customize table names and column name for plugins, you can use the `schema` property in the plugin config: ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { twoFactor } from "better-auth/plugins"; export const auth = betterAuth({ plugins: [ twoFactor({ schema: { user: { fields: { twoFactorEnabled: "two_factor_enabled", secret: "two_factor_secret", }, }, }, }), ], }); ``` ### Extending Core Schema Better Auth provides a type-safe way to extend the `user` and `session` schemas. You can add custom fields to your auth config, and the CLI will automatically update the database schema. These additional fields will be properly inferred in functions like `useSession`, `signUp.email`, and other endpoints that work with user or session objects. To add custom fields, use the `additionalFields` property in the `user` or `session` object of your auth config. The `additionalFields` object uses field names as keys, with each value being a `FieldAttributes` object containing: * `type`: The data type of the field (e.g., "string", "number", "boolean"). * `required`: A boolean indicating if the field is mandatory. * `defaultValue`: The default value for the field (note: this only applies in the JavaScript layer; in the database, the field will be optional). * `input`: This determines whether a value can be provided when creating a new record (default: `true`). If there are additional fields, like `role`, that should not be provided by the user during signup, you can set this to `false`. Here's an example of how to extend the user schema with additional fields: ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ user: { additionalFields: { role: { type: "string", required: false, defaultValue: "user", input: false, // don't allow user to set role }, lang: { type: "string", required: false, defaultValue: "en", }, }, }, }); ``` Now you can access the additional fields in your application logic. ```ts //on signup const res = await auth.api.signUpEmail({ email: "test@example.com", password: "password", name: "John Doe", lang: "fr", }); //user object res.user.role; // > "admin" res.user.lang; // > "fr" ``` See the [TypeScript](/docs/concepts/typescript#inferring-additional-fields-on-client) documentation for more information on how to infer additional fields on the client side. If you're using social / OAuth providers, you may want to provide `mapProfileToUser` to map the profile data to the user object. So, you can populate additional fields from the provider's profile. **Example: Mapping Profile to User For `firstName` and `lastName`** ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ socialProviders: { github: { clientId: "YOUR_GITHUB_CLIENT_ID", clientSecret: "YOUR_GITHUB_CLIENT_SECRET", mapProfileToUser: (profile) => { return { firstName: profile.name.split(" ")[0], lastName: profile.name.split(" ")[1], }; }, }, google: { clientId: "YOUR_GOOGLE_CLIENT_ID", clientSecret: "YOUR_GOOGLE_CLIENT_SECRET", mapProfileToUser: (profile) => { return { firstName: profile.given_name, lastName: profile.family_name, }; }, }, }, }); ``` ### ID Generation Better Auth by default will generate unique IDs for users, sessions, and other entities. If you want to customize how IDs are generated, you can configure this in the `advanced.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** ```ts title="auth.ts" 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** ```typescript title="auth.ts" 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`. ```typescript title="auth.ts" import { betterAuth } from "better-auth"; import { APIError } from "better-auth/api"; export const auth = betterAuth({ databaseHooks: { user: { create: { before: async (user, 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. ```typescript title="auth.ts" 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](/docs/concepts/hooks#ctx). ## 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](/docs/plugins/email-otp) 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. ```ts title="auth.ts" import { betterAuth } from 'better-auth'; import { sendEmail } from './email'; // your email sending function export const auth = betterAuth({ emailVerification: { sendVerificationEmail: async ({ user, url, token }, request) => { await sendEmail({ to: user.email, subject: 'Verify your email address', text: `Click the link to verify your email: ${url}` }) } } }) ``` ### Triggering Email Verification You can initiate email verification in several ways: #### 1. During Sign-up To automatically send a verification email at signup, set `emailVerification.sendOnSignUp` to `true`. ```ts title="auth.ts" import { betterAuth } from 'better-auth'; export const auth = betterAuth({ emailVerification: { sendOnSignUp: true } }) ``` This sends a verification email when a user signs up. For social logins, email verification status is read from the SSO. With `sendOnSignUp` enabled, when the user logs in with an SSO that does not claim the email as verified, Better Auth will dispatch a verification email, but the verification is not required to login even when `requireEmailVerification` is enabled. #### 2. Require Email Verification If you enable require email verification, users must verify their email before they can log in. And every time a user tries to sign in, `sendVerificationEmail` is called. This only works if you have `sendVerificationEmail` implemented and if the user is trying to sign in with email and password. ```ts title="auth.ts" export const auth = betterAuth({ emailAndPassword: { requireEmailVerification: true } }) ``` if a user tries to sign in without verifying their email, you can handle the error and show a message to the user. ```ts title="auth-client.ts" await authClient.signIn.email({ email: "email@example.com", password: "password" }, { onError: (ctx) => { // Handle the error if(ctx.error.status === 403) { alert("Please verify your email address") } //you can also show the original error message alert(ctx.error.message) } }) ``` #### 3. Manually You can also manually trigger email verification by calling `sendVerificationEmail`. ```ts await authClient.sendVerificationEmail({ email: "user@email.com", callbackURL: "/" // The redirect URL after verification }) ``` ### Verifying the Email If the user clicks the provided verification URL, their email is automatically verified, and they are redirected to the `callbackURL`. For manual verification, you can send the user a custom link with the `token` and call the `verifyEmail` function. ```ts 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`: ```ts 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: ```ts title="auth.ts" 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. ```ts title="auth.ts" import { betterAuth } from 'better-auth'; import { sendEmail } from './email'; // your email sending function export const auth = betterAuth({ emailAndPassword: { enabled: true, sendResetPassword: async ({ user, url, token }, request) => { await sendEmail({ to: user.email, subject: 'Reset your password', text: `Click the link to reset your password: ${url}` }) } } }) ``` Check out the [Email and Password](/docs/authentication/email-password#forget-password) guide for more details on how to implement password reset in your app. Also you can check out the [Otp verification](/docs/plugins/email-otp#reset-password) 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`: ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { createAuthMiddleware, APIError } from "better-auth/api"; export const auth = betterAuth({ hooks: { before: createAuthMiddleware(async (ctx) => { if (ctx.path !== "/sign-up/email") { return; } if (!ctx.body?.email.endsWith("@example.com")) { throw new APIError("BAD_REQUEST", { message: "Email must end with @example.com", }); } }), }, }); ``` ### Example: Modify Request Context To adjust the request context before proceeding: ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { createAuthMiddleware } from "better-auth/api"; export const auth = betterAuth({ hooks: { before: createAuthMiddleware(async (ctx) => { if (ctx.path === "/sign-up/email") { return { context: { ...ctx, body: { ...ctx.body, name: "John Doe", }, } }; } }), }, }); ``` ## After Hooks **After hooks** run *after* an endpoint is executed. Use them to modify responses. ### Example: Send a notification to your channel when a new user is registered ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { createAuthMiddleware } from "better-auth/api"; import { sendMessage } from "@/lib/notification" export const auth = betterAuth({ hooks: { after: createAuthMiddleware(async (ctx) => { if(ctx.path.startsWith("/sign-up")){ const newSession = ctx.context.newSession; if(newSession){ sendMessage({ type: "user-register", name: newSession.user.name, }) } } }), }, }); ``` ## Ctx When you call `createAuthMiddleware` a `ctx` object is passed that provides a lot of useful properties. Including: * **Path:** `ctx.path` to get the current endpoint path. * **Body:** `ctx.body` for parsed request body (available for POST requests). * **Headers:** `ctx.headers` to access request headers. * **Request:** `ctx.request` to access the request object (may not exist in server-only endpoints). * **Query Parameters:** `ctx.query` to access query parameters. * **Context**: `ctx.context` auth related context, useful for accessing new session, auth cookies configuration, password hashing, config... and more. ### Request Response This utilities allows you to get request information and to send response from a hook. #### JSON Responses Use `ctx.json` to send JSON responses: ```ts const hook = createAuthMiddleware(async (ctx) => { return ctx.json({ message: "Hello World", }); }); ``` #### Redirects Use `ctx.redirect` to redirect users: ```ts import { createAuthMiddleware } from "better-auth/api"; const hook = createAuthMiddleware(async (ctx) => { throw ctx.redirect("/sign-up/name"); }); ``` #### Cookies * Set cookies: `ctx.setCookies` or `ctx.setSignedCookie`. * Get cookies: `ctx.getCookies` or `ctx.getSignedCookies`. Example: ```ts import { createAuthMiddleware } from "better-auth/api"; const hook = createAuthMiddleware(async (ctx) => { ctx.setCookies("my-cookie", "value"); await ctx.setSignedCookie("my-signed-cookie", "value", ctx.context.secret, { maxAge: 1000, }); const cookie = ctx.getCookies("my-cookie"); const signedCookie = await ctx.getSignedCookies("my-signed-cookie"); }); ``` #### Errors Throw errors with `APIError` for a specific status code and message: ```ts import { createAuthMiddleware, APIError } from "better-auth/api"; const hook = createAuthMiddleware(async (ctx) => { throw new APIError("BAD_REQUEST", { message: "Invalid request", }); }); ``` ### Context The `ctx` object contains another `context` object inside that's meant to hold contexts related to auth. Including a newly created session on after hook, cookies configuration, password hasher and so on. #### New Session The newly created session after an endpoint is run. This only exist in after hook. ```ts title="auth.ts" createAuthMiddleware(async (ctx) => { const newSession = ctx.context.newSession }); ``` #### Returned The returned value from the hook is passed to the next hook in the chain. ```ts title="auth.ts" 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. ```ts title="auth.ts" createAuthMiddleware(async (ctx) => { const responseHeaders = ctx.context.responseHeaders; }); ``` #### Predefined Auth Cookies Access BetterAuth’s predefined cookie properties: ```ts title="auth.ts" createAuthMiddleware(async (ctx) => { const cookieName = ctx.context.authCookies.sessionToken.name; }); ``` #### Secret You can access the `secret` for your auth instance on `ctx.context.secret` #### Password The password object provider `hash` and `verify` * `ctx.context.password.hash`: let's you hash a given password. * `ctx.context.password.verify`: let's you verify given `password` and a `hash`. #### Adapter Adapter exposes the adapter methods used by Better Auth. Including `findOne`, `findMany`, `create`, `delete`, `update` and `updateMany`. You generally should use your actually `db` instance from your orm rather than this adapter. #### Internal Adapter These are calls to your db that perform specific actions. `createUser`, `createSession`, `updateSession`... This may be useful to use instead of using your db directly to get access to `databaseHooks`, proper `secondaryStorage` support and so on. If you're make a query similar to what exist in this internal adapter actions it's worth a look. #### generateId You can use `ctx.context.generateId` to generate Id for various reasons. ## Reusable Hooks If you need to reuse a hook across multiple endpoints, consider creating a plugin. Learn more in the [Plugins Documentation](/docs/concepts/plugins). # 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](/docs/plugins/generic-oauth) for custom integrations. ## Configuring Social Providers To enable a social provider, you need to provide `clientId` and `clientSecret` for the provider. Here's an example of how to configure Google as a provider: ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ // Other configurations... socialProviders: { google: { clientId: "YOUR_GOOGLE_CLIENT_ID", clientSecret: "YOUR_GOOGLE_CLIENT_SECRET", }, }, }); ``` ## 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. ```ts // client-side usage await authClient.signIn.social({ provider: "google", // or any other provider id }) ``` ```ts // server-side usage await auth.api.signInSocial({ body: { provider: "google", // or any other provider id }, }); ``` ### Link account To link an account to a social provider, you can use the `linkAccount` function with the `authClient` or `auth.api` for server-side usage. ```ts await authClient.linkSocial({ provider: "google", // or any other provider id }) ``` server-side usage: ```ts 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. ```ts 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: ```ts 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. ```ts 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: ```ts 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. ```ts 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. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ // Other configurations... socialProviders: { google: { clientId: "YOUR_GOOGLE_CLIENT_ID", clientSecret: "YOUR_GOOGLE_CLIENT_SECRET", mapProfileToUser: (profile) => { return { firstName: profile.given_name, lastName: profile.family_name, }; }, }, }, }); ``` ## How OAuth Works in Better Auth Here's what happens when a user selects a provider to authenticate with: 1. **Configuration Check:** Ensure the necessary provider details (e.g., client ID, secret) are configured. 2. **State Generation:** Generate and save a state token in your database for CSRF protection. 3. **PKCE Support:** If applicable, create a PKCE code challenge and verifier for secure exchanges. 4. **Authorization URL Construction:** Build the provider's authorization URL with parameters like client ID, redirect URI, state, etc. The callback URL usually follows the pattern `/api/auth/callback/${providerName}`. 5. **User Redirection:** * If redirection is enabled, users are redirected to the provider's login page. * If redirection is disabled, the authorization URL is returned for the client to handle the redirection. ### Post-Login Flow After the user completes the login process, the provider redirects them back to the callback URL with a code and state. Better Auth handles the rest: 1. **Token Exchange:** The code is exchanged for an access token and user information. 2. **User Handling:** * If the user doesn't exist, a new account is created. * If the user exists, they are logged in. * If the user has multiple accounts across providers, Better Auth links them based on your configuration. Learn more about [account linking](/docs/concepts/users-accounts#account-linking). 3. **Session Creation:** A new session is created for the user. 4. **Redirect:** Users are redirected to the specified URL provided during the initial request or `/`. If any error occurs during the process, Better Auth handles it and redirects the user to the error URL (if provided) or the callbackURL. And it includes the error message in the query string `?error=...`. # 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. ```ts title="server.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ plugins: [ // Add your plugins here ] }); ``` Client plugins are added when creating the client. Most plugin require both server and client plugins to work correctly. The Better Auth auth client on the frontend uses the `createAuthClient` function provided by `better-auth/client`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client"; const authClient = createAuthClient({ plugins: [ // Add your client plugins here ] }); ``` We recommend keeping the auth-client and your normal auth instance in separate files. ## Creating a Plugin To get started, you'll need a server plugin. Server plugins are the backbone of all plugins, and client plugins are there to provide an interface with frontend APIs to easily work with your server plugins. If your server plugins has endpoints that needs to be called from the client, you'll also need to create a client plugin. ### What can a plugin do? * Create custom `endpoint`s to perform any action you want. * Extend database tables with custom `schemas`. * Use a `middleware` to target a group of routes using it's route matcher, and run only when those routes are called through a request. * Use `hooks` to target a specific route or request. And if you want to run the hook even if the endpoint is called directly. * Use `onRequest` or `onResponse` if you want to do something that affects all requests or responses. * Create custom `rate-limit` rule. ## Create a Server plugin To create a server plugin you need to pass an object that satisfies the `BetterAuthPlugin` interface. The only required property is `id`, which is a unique identifier for the plugin. Both server and client plugins can use the same `id`. ```ts title="plugin.ts" import type { BetterAuthPlugin } from "better-auth"; export const myPlugin = ()=>{ return { id: "my-plugin", } satisfies BetterAuthPlugin } ``` You don't have to make the plugin a function, but it's recommended to do so. This way you can pass options to the plugin and it's consistent with the built-in plugins. ### Endpoints To add endpoints to the server, you can pass `endpoints` which requires an object with the key being any `string` and the value being an `AuthEndpoint`. To create an Auth Endpoint you'll need to import `createAuthEndpoint` from `better-auth`. Better Auth uses wraps around another library called Better Call to create endpoints. Better call is a simple ts web framework made by the same team behind Better Auth. ```ts title="plugin.ts" import { createAuthEndpoint } from "better-auth/api"; const myPlugin = ()=> { return { id: "my-plugin", endpoints: { getHelloWorld: createAuthEndpoint("/my-plugin/hello-world", { method: "GET", }, async(ctx) => { return ctx.json({ message: "Hello World" }) }) } } satisfies BetterAuthPlugin } ``` Create Auth endpoints wraps around `createEndpoint` from Better Call. Inside the `ctx` object, it'll provide another object called `context` that give you access better-auth specific contexts including `options`, `db`, `baseURL` and more. **Context Object** * `appName`: The name of the application. Defaults to "Better Auth". * `options`: The options passed to the Better Auth instance. * `tables`: Core tables definition. It is an object which has the table name as the key and the schema definition as the value. * `baseURL`: the baseURL of the auth server. This includes the path. For example, if the server is running on `http://localhost:3000`, the baseURL will be `http://localhost:3000/api/auth` by default unless changed by the user. * `session`: The session configuration. Includes `updateAge` and `expiresIn` values. * `secret`: The secret key used for various purposes. This is defined by the user. * `authCookie`: The default cookie configuration for core auth cookies. * `logger`: The logger instance used by Better Auth. * `db`: The Kysely instance used by Better Auth to interact with the database. * `adapter`: This is the same as db but it give you `orm` like functions to interact with the database. (we recommend using this over `db` unless you need raw sql queries or for performance reasons) * `internalAdapter`: These are internal db calls that are used by Better Auth. For example, you can use these calls to create a session instead of using `adapter` directly. `internalAdapter.createSession(userId)` * `createAuthCookie`: This is a helper function that let's you get a cookie `name` and `options` for either to `set` or `get` cookies. It implements things like `__secure` prefix and `__host` prefix for cookies based on For other properties, you can check the Better Call documentation and the source code . **Rules for Endpoints** * Makes sure you use kebab-case for the endpoint path * Make sure to only use `POST` or `GET` methods for the endpoints. * Any function that modifies a data should be a `POST` method. * Any function that fetches data should be a `GET` method. * Make sure to use the `createAuthEndpoint` function to create API endpoints. * Make sure your paths are unique to avoid conflicts with other plugins. If you're using a common path, add the plugin name as a prefix to the path. (`/my-plugin/hello-world` instead of `/hello-world`.) ### Schema You can define a database schema for your plugin by passing a `schema` object. The schema object should have the table name as the key and the schema definition as the value. ```ts title="plugin.ts" import { BetterAuthPlugin } from "better-auth/plugins"; const myPlugin = ()=> { return { id: "my-plugin", schema: { myTable: { fields: { name: { type: "string" } }, modelName: "myTable" // optional if you want to use a different name than the key } } } satisfies BetterAuthPlugin } ``` **Fields** By default better-auth will create an `id` field for each table. You can add additional fields to the table by adding them to the `fields` object. The key is the column name and the value is the column definition. The column definition can have the following properties: `type`: The type of the 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`) ```ts title="plugin.ts" const myPlugin = (opts: PluginOptions)=>{ return { id: "my-plugin", schema: { rateLimit: { fields: { key: { type: "string", }, }, disableMigration: opts.storage.provider !== "database", // [!code highlight] }, }, } satisfies BetterAuthPlugin } ``` if you add additional fields to a `user` or `session` table, the types will be inferred automatically on `getSession` and `signUpEmail` calls. ```ts title="plugin.ts" const myPlugin = ()=>{ return { id: "my-plugin", schema: { user: { fields: { age: { type: "number", }, }, }, }, } satisfies BetterAuthPlugin } ``` This will add an `age` field to the `user` table and all `user` returning endpoints will include the `age` field and it'll be inferred properly by typescript. Don't store sensitive information in `user` or `session` table. Crate a new table if you need to store sensitive information. ### Hooks Hooks are used to run code before or after an action is performed, either from a client or directly on the server. You can add hooks to the server by passing a `hooks` object, which should contain `before` and `after` properties. ```ts title="plugin.ts" import { createAuthMiddleware } from "better-auth/plugins"; const myPlugin = ()=>{ return { id: "my-plugin", hooks: { before: [{ matcher: (context)=>{ return context.headers.get("x-my-header") === "my-value" }, handler: createAuthMiddleware(async (ctx)=>{ //do something before the request return { context: ctx // if you want to modify the context } }) }], after: [{ matcher: (context)=>{ return context.path === "/sign-up/email" }, handler: 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. ```ts title="plugin.ts" 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. Here’s how it works: * **Continue as Normal**: If you don't return anything, the request will proceed as usual. * **Interrupt the Request**: To stop the request and send a response, return an object with a `response` property that contains a `Response` object. * **Modify the Request**: You can also return a modified `request` object to change the request before it's sent. ```ts title="plugin.ts" const myPlugin = ()=> { return { id: "my-plugin", onRequest: async (request, context) => { //do something }, } satisfies BetterAuthPlugin } ``` #### On Response The `onResponse` function is executed immediately after a response is returned. It takes two parameters: the `response` and the `context` object. Here’s how to use it: * **Modify the Response**: You can return a modified response object to change the response before it is sent to the client. * **Continue Normally**: If you don't return anything, the response will be sent as is. ```ts title="plugin.ts" const myPlugin = ()=>{ return { id: "my-plugin", onResponse: async (response, context) => { //do something }, } satisfies BetterAuthPlugin } ``` ### Rate Limit You can define custom rate limit rules for your plugin by passing a `rateLimit` array. The rate limit array should contain an array of rate limit objects. ```ts title="plugin.ts" const myPlugin = ()=>{ return { id: "my-plugin", rateLimit: [ { pathMatcher: (path)=>{ return path === "/my-plugin/hello-world" }, limit: 10, window: 60, } ] } satisfies BetterAuthPlugin } ``` ### Server-plugin helper functions Some additional helper functions for creating server plugins. #### `getSessionFromCtx` Allows you to get the client's session data by passing the auth middleware's `context`. ```ts title="plugin.ts" import { createAuthMiddleware } from "better-auth/plugins"; const myPlugin = { id: "my-plugin", hooks: { before: [{ matcher: (context)=>{ return context.headers.get("x-my-header") === "my-value" }, handler: createAuthMiddleware(async (ctx) => { const session = await getSessionFromCtx(ctx); //do something with the client's session. return { context: ctx } }) }], } } satisfies BetterAuthPlugin ``` #### `sessionMiddleware` A middleware that checks if the client has a valid session. If the client has a valid session, it'll add the session data to the context object. ```ts title="plugin.ts" import { createAuthMiddleware } from "better-auth/plugins"; import { sessionMiddleware } from "better-auth/api"; const myPlugin = ()=>{ return { id: "my-plugin", endpoints: { getHelloWorld: createAuthEndpoint("/my-plugin/hello-world", { method: "GET", use: [sessionMiddleware], // [!code highlight] }, async(ctx) => { const session = ctx.context.session; return ctx.json({ message: "Hello World" }) }) } } satisfies BetterAuthPlugin } ``` ## Creating a client plugin If your endpoints needs to be called from the client, you'll need to also create a client plugin. Better Auth clients can infer the endpoints from the server plugins. You can also add additional client side logic. ```ts title="client-plugin.ts" import type { BetterAuthClientPlugin } from "better-auth"; export const myPluginClient = ()=>{ return { id: "my-plugin", } satisfies BetterAuthClientPlugin } ``` ### Endpoint Interface Endpoints are inferred from the server plugin by adding a `$InferServerPlugin` key to the client plugin. The client infers the `path` as an object and converts kebab-case to camelCase. For example, `/my-plugin/hello-world` becomes `myPlugin.helloWorld`. ```ts title="client-plugin.ts" import type { BetterAuthClientPlugin } from "better-auth/client"; import type { myPlugin } from "./plugin"; const myPluginClient = ()=> { return { id: "my-plugin", $InferServerPlugin: {} as ReturnType, } satisfies BetterAuthClientPlugin } ``` ### Get actions If you need to add additional methods or what not to the client you can use the `getActions` function. This function is called with the `fetch` function from the client. Better Auth uses Better fetch to make requests. Better fetch is a simple fetch wrapper made by the same author of Better Auth. ```ts title="client-plugin.ts" import type { BetterAuthClientPlugin } from "better-auth/client"; import type { myPlugin } from "./plugin"; import type { BetterFetchOption } from "@better-fetch/fetch"; const myPluginClient = { id: "my-plugin", $InferServerPlugin: {} as ReturnType, getActions: ($fetch)=>{ return { myCustomAction: async (data: { foo: string, }, fetchOptions?: BetterFetchOption)=>{ const res = $fetch("/custom/action", { method: "POST", body: { foo: data.foo }, ...fetchOptions }) return res } } } } satisfies BetterAuthClientPlugin ``` As a general guideline, ensure that each function accepts only one argument, with an optional second argument for fetchOptions to allow users to pass additional options to the fetch call. The function should return an object containing data and error keys. If your use case involves actions beyond API calls, feel free to deviate from this rule. ### Get Atoms This is only useful if you want to provide `hooks` like `useSession`. Get atoms is called with the `fetch` function from better fetch and it should return an object with the atoms. The atoms should be created using nanostores. The atoms will be resolved by each framework `useStore` hook provided by nanostores. ```ts title="client-plugin.ts" import { atom } from "nanostores"; import type { BetterAuthClientPlugin } from "better-auth/client"; const myPluginClient = { id: "my-plugin", $InferServerPlugin: {} as ReturnType, getAtoms: ($fetch)=>{ const myAtom = atom() return { myAtom } } } satisfies BetterAuthClientPlugin ``` See built-in plugins for examples of how to use atoms properly. ### Path methods By default, inferred paths use `GET` method if they don't require a body and `POST` if they do. You can override this by passing a `pathMethods` object. The key should be the path and the value should be the method ("POST" | "GET"). ```ts title="client-plugin.ts" import type { BetterAuthClientPlugin } from "better-auth/client"; import type { myPlugin } from "./plugin"; const myPluginClient = { id: "my-plugin", $InferServerPlugin: {} as ReturnType, pathMethods: { "/my-plugin/hello-world": "POST" } } satisfies BetterAuthClientPlugin ``` ### Fetch plugins If you need to use better fetch plugins you can pass them to the `fetchPlugins` array. You can read more about better fetch plugins in the better fetch documentation. ### Atom Listeners This is only useful if you want to provide `hooks` like `useSession` and you want to listen to atoms and re-evaluate them when they change. You can see how this is used in the built-in plugins. # 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. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ rateLimit: { window: 10, // time window in seconds max: 100, // max requests in the window }, }) ``` Rate limiting is disabled in development mode by default. In order to enable it, set `enabled` to `true`: ```ts title="auth.ts" 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. ```ts title="auth.ts" 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 ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ //...other options rateLimit: { window: 60, // time window in seconds max: 100, // max requests in the window }, }) ``` You can also pass custom rules for specific paths. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ //...other options rateLimit: { window: 60, // time window in seconds max: 100, // max requests in the window customRules: { "/sign-in/email": { window: 10, max: 3, }, "/two-factor/*": async (request)=> { // custom function to return rate limit window and max return { window: 10, max: 3, } } }, }, }) ``` 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. ```ts title="auth.ts" 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** ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ //...other options rateLimit: { storage: "database", modelName: "rateLimit", //optional by default "rateLimit" is used }, }) ``` Make sure to run `migrate` to create the rate limit table in your database. ```bash npx @better-auth/cli migrate ``` **Using Secondary Storage** If a [Secondary Storage](/docs/concepts/database#secondary-storage) has been configured you can use that to store rate limit data. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ //...other options rateLimit: { storage: "secondary-storage" }, }) ``` **Custom Storage** If none of the above solutions suits your use case you can implement a `customStorage`. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ //...other options rateLimit: { customStorage: { get: async (key) => { // get rate limit data }, set: async (key, value) => { // set rate limit data }, }, }, }) ``` ## Handling Rate Limit Errors When a request exceeds the rate limit, Better Auth returns the following header: * `X-Retry-After`: The number of seconds until the user can make another request. To handle rate limit errors on the client side, you can manage them either globally or on a per-request basis. Since Better Auth clients wrap over Better Fetch, you can pass `fetchOptions` to handle rate limit errors **Global Handling** ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client"; export const authClient = createAuthClient({ fetchOptions: { onError: async (context) => { const { response } = context; if (response.status === 429) { const retryAfter = response.headers.get("X-Retry-After"); console.log(`Rate limit exceeded. Retry after ${retryAfter} seconds`); } }, } }) ``` **Per Request Handling** ```ts title="auth-client.ts" import { 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` # 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. ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ //... other config options session: { expiresIn: 60 * 60 * 24 * 7, // 7 days updateAge: 60 * 60 * 24 // 1 day (every 1 day the session expiration is updated) } }) ``` ### Disable Session Refresh You can disable session refresh so that the session is not updated regardless of the `updateAge` option. ```ts title="auth.ts" 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: ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ //... other config options session: { freshAge: 60 * 5 // 5 minutes (the session is fresh if created within the last 5 minutes) } }) ``` To **disable the freshness check**, set `freshAge` to `0`: ```ts title="auth.ts" import { betterAuth } from "better-auth" export const auth = betterAuth({ //... other config options session: { freshAge: 0 // Disable freshness check } }) ``` ## Session Management Better Auth provides a set of functions to manage sessions. ### Get Session The `getSession` function retrieves the current active session. ```ts client="client.ts" import { authClient } from "@/lib/client" const { data: session } = await authClient.getSession() ``` To learn how to customize the session response check the [Customizing Session Response](#customizing-session-response) section. ### Use Session The `useSession` action provides a reactive way to access the current session. ```ts client="client.ts" import { authClient } from "@/lib/client" const { data: session } = authClient.useSession() ``` ### List Sessions The `listSessions` function returns a list of sessions that are active for the user. ```ts title="auth-client.ts" import { authClient } from "@/lib/client" const sessions = await authClient.listSessions() ``` ### Revoke Session When a user signs out of a device, the session is automatically ended. However, you can also end a session manually from any device the user is signed into. To end a session, use the `revokeSession` function. Just pass the session token as a parameter. ```ts title="auth-client.ts" await authClient.revokeSession({ token: "session-token" }) ``` ### Revoke Other Sessions To revoke all other sessions except the current session, you can use the `revokeOtherSessions` function. ```ts title="auth-client.ts" await authClient.revokeOtherSessions() ``` ### Revoke All Sessions To revoke all sessions, you can use the `revokeSessions` function. ```ts title="auth-client.ts" await authClient.revokeSessions() ``` ### Revoking Sessions on Password Change You can revoke all sessions when the user changes their password by passing `revokeOtherSessions` as true on `changePassword` function. ```ts title="auth.ts" await authClient.changePassword({ newPassword: newPassword, currentPassword: currentPassword, revokeOtherSessions: true, }) ``` ## Session Caching ### Cookie Cache Calling your database every time `useSession` or `getSession` invoked isn’t ideal, especially if sessions don’t change frequently. Cookie caching handles this by storing session data in a short-lived, signed cookie—similar to how JWT access tokens are used with refresh tokens. When cookie caching is enabled, the server can check session validity from the cookie itself instead of hitting the database each time. The cookie is signed to prevent tampering, and a short `maxAge` ensures that the session data gets refreshed regularly. If a session is revoked or expires, the cookie will be invalidated automatically. To turn on cookie caching, just set `session.cookieCache` in your auth config: ```ts title="auth.ts" 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. ```ts title="auth-client.ts" const session = await authClient.getSession({ query: { disableCookieCache: true }}) ``` or on the server ```ts title="server.ts" 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. ```ts title="auth.ts" import { customSession } from "better-auth/plugins"; export const auth = betterAuth({ plugins: [ customSession(async ({ user, session }) => { const roles = findUserRoles(session.session.userId); return { roles, user: { ...user, newField: "newField", }, session }; }), ], }); ``` This will add `roles` and `user.newField` to the session response. **Infer on the Client** ```ts title="auth-client.ts" import { customSessionClient } from "better-auth/client/plugins"; import type { auth } from "@/lib/auth"; // Import the auth instance as a type const authClient = createAuthClient({ plugins: [customSessionClient()], }); const { data } = 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. ```ts import { betterAuth, BetterAuthOptions } from "better-auth"; const options = { //...config options plugins: [ //...plugins ] } satisfies BetterAuthOptions; export const auth = betterAuth({ ...options, plugins: [ ...(options.plugins ?? []), customSession(async ({ user, session }, 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] ] }) ``` 2. 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. 3. 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](/docs/plugins/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. ```ts title="auth.ts" 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: ```json title="tsconfig.json" { "compilerOptions": { "strict": true } } ``` if you can't set `strict` to `true`, you can enable `strictNullChecks`: ```json title="tsconfig.json" { "compilerOptions": { "strictNullChecks": true, } } ``` 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`. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client" const authClient = createAuthClient() export type Session = typeof authClient.$Infer.Session ``` The `Session` type includes both `session` and `user` properties. The user property represents the user object type, and the `session` property represents the `session` object type. You can also infer types on the server side. ```ts title="auth.ts" import { betterAuth } from "better-auth" import Database from "better-sqlite3" export const auth = betterAuth({ database: new Database("database.db") }) type Session = typeof auth.$Infer.Session ``` ## Additional Fields Better Auth allows you to add additional fields to the user and session objects. All additional fields are properly inferred and available on the server and client side. ```ts import { betterAuth } from "better-auth" import Database from "better-sqlite3" export const auth = betterAuth({ database: new Database("database.db"), user: { additionalFields: { role: { type: "string", 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`: ```ts 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. ```ts import { inferAdditionalFields } from "better-auth/client/plugins"; import { createAuthClient } from "better-auth/react"; import type { auth } from "./auth"; export const authClient = createAuthClient({ plugins: [inferAdditionalFields()], }); ``` 2. For Separate Client-Server Projects If your client and server are in separate projects, you'll need to manually specify the additional fields when creating the auth client. ```ts import { 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](/docs/concepts/database#user). The user table can be extended using [additional fields](/docs/concepts/database#extending-core-schema) or by plugins to store additional data. ## Update User ### Update User Information To update user information, you can use the `updateUser` function provided by the client. The `updateUser` function takes an object with the following properties: ```ts await authClient.updateUser({ image: "https://example.com/image.jpg", name: "John Doe", }) ``` ### Change Email To allow users to change their email, first enable the `changeEmail` feature, which is disabled by default. Set `changeEmail.enabled` to `true`: ```ts export const auth = betterAuth({ user: { changeEmail: { enabled: true, } } }) ``` For users with a verified email, provide the `sendChangeEmailVerification` function. This function triggers when a user changes their email, sending a verification email with a URL and token. If the current email isn't verified, the change happens immediately without verification. ```ts export const auth = betterAuth({ user: { changeEmail: { enabled: true, sendChangeEmailVerification: async ({ user, newEmail, url, token }, request) => { await sendEmail({ to: user.email, // verification email must be sent to the current user email to approve the change subject: 'Approve email change', text: `Click the link to approve the change: ${url}` }) } } } }) ``` Once enabled, use the `changeEmail` function on the client to update a user’s email. The user must verify their current email before changing it. ```ts await authClient.changeEmail({ newEmail: "new-email@email.com", callbackURL: "/dashboard", //to redirect after verification }); ``` After verification, the new email is updated in the user table, and a confirmation is sent to the new address. If the current email is unverified, the new email is updated without the verification step. ### Change Password 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 ```ts const { data, error } = await authClient.changePassword({ newPassword: newpassword1234, currentPassword: oldpassword1234, revokeOtherSessions, // required }); ``` ### Server Side ```ts const data = await auth.api.changePassword({ body: { newPassword: newpassword1234, currentPassword: oldpassword1234, revokeOtherSessions, // required }, // This endpoint requires session cookies. headers: await headers() }); ``` ### Type Definition ```ts 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. ```ts 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` ```ts export const auth = betterAuth({ //...other config user: { deleteUser: { // [!code highlight] enabled: true // [!code highlight] } // [!code highlight] } }) ``` Once enabled, you can call `authClient.deleteUser` to permanently delete user data from your database. ### Adding Verification Before Deletion For added security, you’ll likely want to confirm the user’s intent before deleting their account. A common approach is to send a verification email. Better Auth provides a `sendDeleteAccountVerification` utility for this purpose. 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. Here’s how you can set it up: ```ts export const auth = betterAuth({ user: { deleteUser: { enabled: true, sendDeleteAccountVerification: async ( { user, // The user object url, // The auto-generated URL for deletion token // The verification token (can be used to generate custom URL) }, request // The original request object (optional) ) => { // Your email sending logic here // Example: sendEmail(data.user.email, "Verify Deletion", data.url); }, }, }, }); ``` **How callback verification works:** * **Callback URL**: The URL provided in `sendDeleteAccountVerification` is a pre-generated link that deletes the user data when accessed. ```ts title="delete-user.ts" await authClient.deleteUser({ callbackURL: "/goodbye" // you can provide a callback URL to redirect after deletion }); ``` * **Authentication Check**: The user must be signed in to the account they’re attempting to delete. If they aren’t signed in, the deletion process will fail. If you have sent a custom URL, you can use the `deleteUser` method with the token to delete the user. ```ts title="delete-user.ts" await authClient.deleteUser({ token }); ``` ### Authentication Requirements To delete a user, the user must meet one of the following requirements: 1. A valid password if the user has a password, they can delete their account by providing the password. ```ts title="delete-user.ts" await authClient.deleteUser({ password: "password" }); ``` 2. Fresh session The user must have a `fresh` session token, meaning the user must have signed in recently. This is checked if the password is not provided. By default `session.freshAge` is set to `60 * 60 * 24` (1 day). You can change this value by passing the `session` object to the `auth` configuration. If it is set to `0`, the freshness check is disabled. It is recommended not to disable this check if you are not using email verification for deleting the account. ```ts title="delete-user.ts" await authClient.deleteUser(); ``` 3. 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. ```ts title="delete-user.ts" await authClient.deleteUser(); ``` 4. 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. ```ts title="delete-user.ts" await authClient.deleteUser({ token }); ``` ### Callbacks **beforeDelete**: This callback is called before the user is deleted. You can use this callback to perform any cleanup or additional checks before deleting the user. ```ts title="auth.ts" export const auth = betterAuth({ user: { deleteUser: { enabled: true, beforeDelete: async (user) => { // Perform any cleanup or additional checks here }, }, }, }); ``` you can also throw `APIError` to interrupt the deletion process. ```ts title="auth.ts" import { betterAuth } from "better-auth"; import { APIError } from "better-auth/api"; export const auth = betterAuth({ user: { deleteUser: { enabled: true, beforeDelete: async (user, request) => { if (user.email.includes("admin")) { throw new APIError("BAD_REQUEST", { message: "Admin accounts can't be deleted", }); } }, }, }, }); ``` **afterDelete**: This callback is called after the user is deleted. You can use this callback to perform any cleanup or additional actions after the user is deleted. ```ts title="auth.ts" export const auth = betterAuth({ user: { deleteUser: { enabled: true, afterDelete: async (user, request) => { // Perform any cleanup or additional actions here }, }, }, }); ``` ## Accounts Better Auth supports multiple authentication methods. Each authentication method is called a provider. For example, email and password authentication is a provider, Google authentication is a provider, etc. When a user signs in using a provider, an account is created for the user. The account stores the authentication data returned by the provider. This data includes the access token, refresh token, and other information returned by the provider. The account table stores the authentication data of the user [Click here to view the schema](/docs/concepts/database#account) ### List User Accounts To list user accounts you can use `client.user.listAccounts` method. Which will return all accounts associated with a user. ```ts const accounts = await authClient.listAccounts(); ``` ### Token Encryption Better Auth doesn’t encrypt tokens by default and that’s 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 they’re saved to your database. ```ts 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. ```ts title="auth.ts" 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 doesn’t confirm the email verification status. Use this with caution as it may increase the risk of account takeover. ```ts title="auth.ts" 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. ```ts 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: ```ts 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: ```ts 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. ```ts title="auth.ts" 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. ```ts title="auth.ts" 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. ```ts await auth.api.setPassword({ headers: /* headers containing the user's session token */, password: /* new password */ }); ``` `setPassword` can't be called from the client for security reasons. ### Account Unlinking You can unlink a user account by providing a `providerId`. ```ts await authClient.unlinkAccount({ providerId: "google" }); // 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`). ```ts title="auth.ts" export const auth = betterAuth({ account: { accountLinking: { allowUnlinkingAll: true } }, }); ``` # guides: Migrating from Auth0 to Better Auth URL: /docs/guides/auth0-migration-guide Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/guides/auth0-migration-guide.mdx A step-by-step guide to transitioning from Auth0 to Better Auth. *** title: Migrating from Auth0 to Better Auth description: A step-by-step guide to transitioning from Auth0 to Better Auth. ----------------------------------------------------------------------------- In this guide, we'll walk through the steps to migrate a project from Auth0 to Better Auth — including email/password with proper hashing, social/external accounts, two-factor authentication, and more. This migration will invalidate all active sessions. This guide doesn't currently show you how to migrate Organizations but it should be possible with additional steps and the [Organization](/docs/plugins/organization) Plugin. ## Before You Begin Before starting the migration process, set up Better Auth in your project. Follow the [installation guide](/docs/installation) to get started. ### Connect to your database You'll need to connect to your database to migrate the users and accounts. You can use any database you want, but for this example, we'll use PostgreSQL. npm pnpm yarn bun ```bash npm install pg ``` ```bash pnpm add pg ``` ```bash yarn add pg ``` ```bash bun add pg ``` And then you can use the following code to connect to your database. ```ts title="auth.ts" import { Pool } from "pg"; export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL }), }) ``` ### Enable Email and Password (Optional) Enable the email and password in your auth config and implement your own logic for sending verification emails, reset password emails, etc. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL }), emailAndPassword: { // [!code highlight] enabled: true, // [!code highlight] }, // [!code highlight] emailVerification: { sendVerificationEmail: async({ user, url })=>{ // implement your logic here to send email verification } }, }) ``` See [Email and Password](/docs/authentication/email-password) for more configuration options. ### Setup Social Providers (Optional) Add social providers you have enabled in your Auth0 project in your auth config. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL }), emailAndPassword: { enabled: true, }, socialProviders: { // [!code highlight] google: { // [!code highlight] clientId: process.env.GOOGLE_CLIENT_ID, // [!code highlight] clientSecret: process.env.GOOGLE_CLIENT_SECRET, // [!code highlight] }, // [!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] }) ``` ### Add Plugins (Optional) You can add the following plugins to your auth config based on your needs. [Admin](/docs/plugins/admin) Plugin will allow you to manage users, user impersonations and app level roles and permissions. [Two Factor](/docs/plugins/2fa) Plugin will allow you to add two-factor authentication to your application. [Username](/docs/plugins/username) Plugin will allow you to add username authentication to your application. ```ts title="auth.ts" import { Pool } from "pg"; import { betterAuth } from "better-auth"; import { admin, twoFactor, username } from "better-auth/plugins"; export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL }), emailAndPassword: { enabled: true, password: { verify: (data) => { // this for an edgecase that you might run in to on verifying the password } } }, socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }, github: { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, } }, plugins: [admin(), twoFactor(), username()], // [!code highlight] }) ``` ### Generate Schema If you're using a custom database adapter, generate the schema: ```sh npx @better-auth/cli generate ``` or if you're using the default adapter, you can use the following command: ```sh npx @better-auth/cli migrate ``` ### Install Dependencies Install the required dependencies for the migration: ```bash npm install auth0 ``` ### Create the migration script Create a new file called `migrate-auth0.ts` in the `scripts` folder and add the following code: Instead of using the Management API, you can use Auth0's bulk user export functionality and pass the exported JSON data directly to the `auth0Users` array. This is especially useful if you need to migrate password hashes and complete user data, which are not available through the Management API. **Important Notes:** * Password hashes export is only available for Auth0 Enterprise users * Free plan users cannot export password hashes and will need to request a support ticket * For detailed information about bulk user exports, see the [Auth0 Bulk User Export Documentation](https://auth0.com/docs/manage-users/user-migration/bulk-user-exports) * For password hash export details, refer to [Exporting Password Hashes](https://auth0.com/docs/troubleshoot/customer-support/manage-subscriptions/export-data#user-passwords) Example: ```ts // Replace this with your exported users JSON data const auth0Users = [ { "email": "helloworld@gmail.com", "email_verified": false, "name": "Hello world", // Note: password_hash is only available for Enterprise users "password_hash": "$2b$10$w4kfaZVjrcQ6ZOMiG.M8JeNvnVQkPKZV03pbDUHbxy9Ug0h/McDXi", // ... other user data } ]; ``` ```ts title="scripts/migrate-auth0.ts" import { ManagementClient } from 'auth0'; import { generateRandomString, symmetricEncrypt } from "better-auth/crypto"; import { auth } from '@/lib/auth'; const auth0Client = new ManagementClient({ domain: process.env.AUTH0_DOMAIN!, clientId: process.env.AUTH0_CLIENT_ID!, clientSecret: process.env.AUTH0_SECRET!, }); function safeDateConversion(timestamp?: string | number): Date { if (!timestamp) return new Date(); const numericTimestamp = typeof timestamp === 'string' ? Date.parse(timestamp) : timestamp; const milliseconds = numericTimestamp < 1000000000000 ? numericTimestamp * 1000 : numericTimestamp; const date = new Date(milliseconds); if (isNaN(date.getTime())) { console.warn(`Invalid timestamp: ${timestamp}, falling back to current date`); return new Date(); } // Check for unreasonable dates (before 2000 or after 2100) const year = date.getFullYear(); if (year < 2000 || year > 2100) { console.warn(`Suspicious date year: ${year}, falling back to current date`); return new Date(); } return date; } // Helper function to generate backup codes for 2FA async function generateBackupCodes(secret: string) { const key = secret; const backupCodes = Array.from({ length: 10 }) .fill(null) .map(() => generateRandomString(10, "a-z", "0-9", "A-Z")) .map((code) => `${code.slice(0, 5)}-${code.slice(5)}`); const encCodes = await symmetricEncrypt({ data: JSON.stringify(backupCodes), key: key, }); return encCodes; } function mapAuth0RoleToBetterAuthRole(auth0Roles: string[]) { if (typeof auth0Roles === 'string') return auth0Roles; if (Array.isArray(auth0Roles)) return auth0Roles.join(','); } // helper function to migrate password from auth0 to better auth for custom hashes and algs async function migratePassword(auth0User: any) { if (auth0User.password_hash) { if (auth0User.password_hash.startsWith('$2a$') || auth0User.password_hash.startsWith('$2b$')) { return auth0User.password_hash; } } if (auth0User.custom_password_hash) { const customHash = auth0User.custom_password_hash; if (customHash.algorithm === 'bcrypt') { const hash = customHash.hash.value; if (hash.startsWith('$2a$') || hash.startsWith('$2b$')) { return hash; } } return JSON.stringify({ algorithm: customHash.algorithm, hash: { value: customHash.hash.value, encoding: customHash.hash.encoding || 'utf8', ...(customHash.hash.digest && { digest: customHash.hash.digest }), ...(customHash.hash.key && { key: { value: customHash.hash.key.value, encoding: customHash.hash.key.encoding || 'utf8' } }) }, ...(customHash.salt && { salt: { value: customHash.salt.value, encoding: customHash.salt.encoding || 'utf8', position: customHash.salt.position || 'prefix' } }), ...(customHash.password && { password: { encoding: customHash.password.encoding || 'utf8' } }), ...(customHash.algorithm === 'scrypt' && { keylen: customHash.keylen, cost: customHash.cost || 16384, blockSize: customHash.blockSize || 8, parallelization: customHash.parallelization || 1 }) }); } return null; } async function migrateMFAFactors(auth0User: any, userId: string | undefined, ctx: any) { if (!userId || !auth0User.mfa_factors || !Array.isArray(auth0User.mfa_factors)) { return; } for (const factor of auth0User.mfa_factors) { try { if (factor.totp && factor.totp.secret) { await ctx.adapter.create({ model: "twoFactor", data: { userId: userId, secret: factor.totp.secret, backupCodes: await generateBackupCodes(factor.totp.secret) } }); } } catch (error) { console.error(`Failed to migrate MFA factor for user ${userId}:`, error); } } } async function migrateOAuthAccounts(auth0User: any, userId: string | undefined, ctx: any) { if (!userId || !auth0User.identities || !Array.isArray(auth0User.identities)) { return; } for (const identity of auth0User.identities) { try { const providerId = identity.provider === 'auth0' ? "credential" : identity.provider.split("-")[0]; await ctx.adapter.create({ model: "account", data: { id: `${auth0User.user_id}|${identity.provider}|${identity.user_id}`, userId: userId, password: await migratePassword(auth0User), providerId: providerId || identity.provider, accountId: identity.user_id, accessToken: identity.access_token, tokenType: identity.token_type, refreshToken: identity.refresh_token, accessTokenExpiresAt: identity.expires_in ? new Date(Date.now() + identity.expires_in * 1000) : undefined, // if you are enterprise user, you can get the refresh tokens or all the tokensets - auth0Client.users.getAllTokensets refreshTokenExpiresAt: identity.refresh_token_expires_in ? new Date(Date.now() + identity.refresh_token_expires_in * 1000) : undefined, scope: identity.scope, idToken: identity.id_token, createdAt: safeDateConversion(auth0User.created_at), updatedAt: safeDateConversion(auth0User.updated_at) }, forceAllowId: true }).catch((error: Error) => { console.error(`Failed to create OAuth account for user ${userId} with provider ${providerId}:`, error); return ctx.adapter.create({ // Try creating without optional fields if the first attempt failed model: "account", data: { id: `${auth0User.user_id}|${identity.provider}|${identity.user_id}`, userId: userId, password: migratePassword(auth0User), providerId: providerId, accountId: identity.user_id, accessToken: identity.access_token, tokenType: identity.token_type, refreshToken: identity.refresh_token, accessTokenExpiresAt: identity.expires_in ? new Date(Date.now() + identity.expires_in * 1000) : undefined, refreshTokenExpiresAt: identity.refresh_token_expires_in ? new Date(Date.now() + identity.refresh_token_expires_in * 1000) : undefined, scope: identity.scope, idToken: identity.id_token, createdAt: safeDateConversion(auth0User.created_at), updatedAt: safeDateConversion(auth0User.updated_at) }, forceAllowId: true }); }); console.log(`Successfully migrated OAuth account for user ${userId} with provider ${providerId}`); } catch (error) { console.error(`Failed to migrate OAuth account for user ${userId}:`, error); } } } async function migrateOrganizations(ctx: any) { try { const organizations = await auth0Client.organizations.getAll(); for (const org of organizations.data || []) { try { await ctx.adapter.create({ model: "organization", data: { id: org.id, name: org.display_name || org.id, slug: (org.display_name || org.id).toLowerCase().replace(/[^a-z0-9]/g, '-'), logo: org.branding?.logo_url, metadata: JSON.stringify(org.metadata || {}), createdAt: safeDateConversion(org.created_at), }, forceAllowId: true }); const members = await auth0Client.organizations.getMembers({ id: org.id }); for (const member of members.data || []) { try { const userRoles = await auth0Client.organizations.getMemberRoles({ id: org.id, user_id: member.user_id }); const role = mapAuth0RoleToBetterAuthRole(userRoles.data?.map(r => r.name) || []); await ctx.adapter.create({ model: "member", data: { id: `${org.id}|${member.user_id}`, organizationId: org.id, userId: member.user_id, role: role, createdAt: new Date() }, forceAllowId: true }); console.log(`Successfully migrated member ${member.user_id} for organization ${org.display_name || org.id}`); } catch (error) { console.error(`Failed to migrate member ${member.user_id} for organization ${org.display_name || org.id}:`, error); } } console.log(`Successfully migrated organization: ${org.display_name || org.id}`); } catch (error) { console.error(`Failed to migrate organization ${org.display_name || org.id}:`, error); } } console.log('Organization migration completed'); } catch (error) { console.error('Failed to migrate organizations:', error); } } async function migrateFromAuth0() { try { const ctx = await auth.$context; const isAdminEnabled = ctx.options?.plugins?.find(plugin => plugin.id === "admin"); const isUsernameEnabled = ctx.options?.plugins?.find(plugin => plugin.id === "username"); const isOrganizationEnabled = ctx.options?.plugins?.find(plugin => plugin.id === "organization"); const perPage = 100; const auth0Users: any[] = []; let pageNumber = 0; while (true) { try { const params = { per_page: perPage, page: pageNumber, include_totals: true, }; const response = (await auth0Client.users.getAll(params)).data as any; const users = response.users || []; if (users.length === 0) break; auth0Users.push(...users); pageNumber++; if (users.length < perPage) break; } catch (error) { console.error('Error fetching users:', error); break; } } console.log(`Found ${auth0Users.length} users to migrate`); for (const auth0User of auth0Users) { try { // Determine if this is a password-based or OAuth user const isOAuthUser = auth0User.identities?.some((identity: any) => identity.provider !== 'auth0'); // Base user data that's common for both types const baseUserData = { id: auth0User.user_id, email: auth0User.email, emailVerified: auth0User.email_verified || false, name: auth0User.name || auth0User.nickname, image: auth0User.picture, createdAt: safeDateConversion(auth0User.created_at), updatedAt: safeDateConversion(auth0User.updated_at), ...(isAdminEnabled ? { banned: auth0User.blocked || false, role: mapAuth0RoleToBetterAuthRole(auth0User.roles || []), } : {}), ...(isUsernameEnabled ? { username: auth0User.username || auth0User.nickname, } : {}), }; const createdUser = await ctx.adapter.create({ model: "user", data: { ...baseUserData, }, forceAllowId: true }); if (!createdUser?.id) { throw new Error('Failed to create user'); } await migrateOAuthAccounts(auth0User, createdUser.id, ctx) console.log(`Successfully migrated user: ${auth0User.email}`); } catch (error) { console.error(`Failed to migrate user ${auth0User.email}:`, error); } } if (isOrganizationEnabled) { await migrateOrganizations(ctx); } // the reset of migration will be here. console.log('Migration completed successfully'); } catch (error) { console.error('Migration failed:', error); throw error; } } migrateFromAuth0() .then(() => { console.log('Migration completed'); process.exit(0); }) .catch((error) => { console.error('Migration failed:', error); process.exit(1); }); ``` Make sure to replace the Auth0 environment variables with your own values: * `AUTH0_DOMAIN` * `AUTH0_CLIENT_ID` * `AUTH0_SECRET` ### Run the migration Run the migration script: ```sh bun run scripts/migrate-auth0.ts # or use your preferred runtime ``` Important considerations: 1. Test the migration in a development environment first 2. Monitor the migration process for any errors 3. Verify the migrated data in Better Auth before proceeding 4. Keep Auth0 installed and configured until the migration is complete 5. The script handles bcrypt password hashes by default. For custom password hashing algorithms, you'll need to modify the `migratePassword` function ### Verify the migration After running the migration, verify that: 1. All users have been properly migrated 2. Social connections are working 3. Password-based authentication is working 4. Two-factor authentication settings are preserved (if enabled) 5. User roles and permissions are correctly mapped ### Update your components Now that the data is migrated, update your components to use Better Auth. Here's an example for the sign-in component: ```tsx title="components/auth/sign-in.tsx" import { authClient } from "better-auth/client"; export const SignIn = () => { const handleSignIn = async () => { const { data, error } = await authClient.signIn.email({ email: "helloworld@gmail.com", password: "helloworld", }); if (error) { console.error(error); return; } // Handle successful sign in }; return (
); }; ```
### Update the middleware Replace your Auth0 middleware with Better Auth's middleware: ```ts title="middleware.ts" import { NextRequest, NextResponse } from "next/server"; import { getSessionCookie } from "better-auth/cookies"; export async function middleware(request: NextRequest) { const sessionCookie = getSessionCookie(request); const { pathname } = request.nextUrl; if (sessionCookie && ["/login", "/signup"].includes(pathname)) { return NextResponse.redirect(new URL("/dashboard", request.url)); } if (!sessionCookie && pathname.startsWith("/dashboard")) { return NextResponse.redirect(new URL("/login", request.url)); } return NextResponse.next(); } export const config = { matcher: ["/dashboard", "/login", "/signup"], }; ``` ### Remove Auth0 Dependencies Once you've verified everything is working correctly with Better Auth, remove Auth0: ```bash npm remove @auth0/auth0-react @auth0/auth0-spa-js @auth0/nextjs-auth0 ```
## Additional Considerations ### Password Migration The migration script handles bcrypt password hashes by default. If you're using custom password hashing algorithms in Auth0, you'll need to modify the `migratePassword` function in the migration script to handle your specific case. ### Role Mapping The script includes a basic role mapping function (`mapAuth0RoleToBetterAuthRole`). Customize this function based on your Auth0 roles and Better Auth role requirements. ### Rate Limiting The migration script includes pagination to handle large numbers of users. Adjust the `perPage` value based on your needs and Auth0's rate limits. ## Wrapping Up Now! You've successfully migrated from Auth0 to Better Auth. Better Auth offers greater flexibility and more features—be sure to explore the [documentation](/docs) to unlock its full potential. # guides: Browser Extension Guide URL: /docs/guides/browser-extension-guide Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/guides/browser-extension-guide.mdx A step-by-step guide to creating a browser extension with Better Auth. *** title: Browser Extension Guide description: A step-by-step guide to creating a browser extension with Better Auth. ----------------------------------------------------------------------------------- In this guide, we'll walk you through the steps of creating a browser extension using Plasmo with Better Auth for authentication. If you would like to view a completed example, you can check out the browser extension example. The Plasmo framework does not provide a backend for the browser extension. This guide assumes you have{" "} a backend setup of Better Auth and are ready to create a browser extension to connect to it. ## Setup & Installations Initialize a new Plasmo project with TailwindCSS and a src directory. ```bash pnpm create plasmo --with-tailwindcss --with-src ``` Then, install the Better Auth package. ```bash pnpm add better-auth ``` To start the Plasmo development server, run the following command. ```bash pnpm dev ``` ## Configure tsconfig Configure the `tsconfig.json` file to include `strict` mode. For this demo, we have also changed the import alias from `~` to `@` and set it to the `src` directory. ```json title="tsconfig.json" { "compilerOptions": { "paths": { "@/_": [ "./src/_" ] }, "strict": true, "baseUrl": "." } } ``` ## Create the client auth instance Create a new file at `src/auth/auth-client.ts` and add the following code. ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/react" export const authClient = createAuthClient({ baseURL: "http://localhost:3000" /* Base URL of your Better Auth backend. */, plugins: [], }); ``` ## Configure the manifest We must ensure the extension knows the URL to the Better Auth backend. Head to your package.json file, and add the following code. ```json title="package.json" { //... "manifest": { "host_permissions": [ "https://URL_TO_YOUR_BACKEND" // localhost works too (e.g. http://localhost:3000) ] } } ``` ## You're now ready! You have now set up Better Auth for your browser extension. Add your desired UI and create your dream extension! To learn more about the client Better Auth API, check out the client documentation. Here's a quick example 😎 ```tsx title="src/popup.tsx" import { authClient } from "./auth/auth-client" function IndexPopup() { const {data, isPending, error} = authClient.useSession(); if(isPending){ return <>Loading... } if(error){ return <>Error: {error.message} } if(data){ return <>Signed in as {data.user.name} } } export default IndexPopup; ``` ## Bundle your extension To get a production build, run the following command. ```bash pnpm build ``` Head over to chrome://extensions and enable developer mode. Click on "Load Unpacked" and navigate to your extension's `build/chrome-mv3-dev` (or `build/chrome-mv3-prod`) directory. To see your popup, click on the puzzle piece icon on the Chrome toolbar, and click on your extension. Learn more about bundling your extension here. ## Configure the server auth instance First, we will need your extension URL. An extension URL formed like this: `chrome-extension://YOUR_EXTENSION_ID`. You can find your extension ID at chrome://extensions. Head to your server's auth file, and make sure that your extension's URL is added to the `trustedOrigins` list. ```ts title="server.ts" import { betterAuth } from "better-auth" import { auth } from "@/auth/auth" export const auth = betterAuth({ trustedOrigins: ["chrome-extension://YOUR_EXTENSION_ID"], }) ``` If you're developing multiple extensions or need to support different browser extensions with different IDs, you can use wildcard patterns: ```ts title="server.ts" export const auth = betterAuth({ trustedOrigins: [ // Support a specific extension ID "chrome-extension://YOUR_EXTENSION_ID", // Or support multiple extensions with wildcard (less secure) "chrome-extension://*" ], }) ``` Using wildcards for extension origins (`chrome-extension://*`) reduces security by trusting all extensions. It's safer to explicitly list each extension ID you trust. Only use wildcards for development and testing. ## That's it! Everything is set up! You can now start developing your extension. 🎉 ## Wrapping Up Congratulations! You've successfully created a browser extension using Better Auth and Plasmo. We highly recommend you visit the Plasmo documentation to learn more about the framework. If you would like to view a completed example, you can check out the browser extension example. If you have any questions, feel free to open an issue on our GitHub repo, or join our Discord server for support. # guides: Migrating from Clerk to Better Auth URL: /docs/guides/clerk-migration-guide Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/guides/clerk-migration-guide.mdx A step-by-step guide to transitioning from Clerk to Better Auth. *** title: Migrating from Clerk to Better Auth description: A step-by-step guide to transitioning from Clerk to Better Auth. ----------------------------------------------------------------------------- In this guide, we'll walk through the steps to migrate a project from Clerk to Better Auth — including email/password with proper hashing, social/external accounts, phone number, two-factor data, and more. This migration will invalidate all active sessions. This guide doesn't currently show you how to migrate Organization but it should be possible with additional steps and the [Organization](/docs/plugins/organization) Plugin. ## Before You Begin Before starting the migration process, set up Better Auth in your project. Follow the [installation guide](/docs/installation) to get started. And go to ### Connect to your database You'll need to connect to your database to migrate the users and accounts. You can use any database you want, but for this example, we'll use PostgreSQL. npm pnpm yarn bun ```bash npm install pg ``` ```bash pnpm add pg ``` ```bash yarn add pg ``` ```bash bun add pg ``` And then you can use the following code to connect to your database. ```ts title="auth.ts" import { Pool } from "pg"; export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL }), }) ``` ### Enable Email and Password (Optional) Enable the email and password in your auth config and implement your own logic for sending verification emails, reset password emails, etc. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL }), emailAndPassword: { // [!code highlight] enabled: true, // [!code highlight] }, // [!code highlight] emailVerification: { sendVerificationEmail: async({ user, url })=>{ // implement your logic here to send email verification } }, }) ``` See [Email and Password](/docs/authentication/email-password) for more configuration options. ### Setup Social Providers (Optional) Add social providers you have enabled in your Clerk project in your auth config. ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL }), emailAndPassword: { enabled: true, }, 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] }) ``` ### Add Plugins (Optional) You can add the following plugins to your auth config based on your needs. [Admin](/docs/plugins/admin) Plugin will allow you to manage users, user impersonations and app level roles and permissions. [Two Factor](/docs/plugins/2fa) Plugin will allow you to add two-factor authentication to your application. [Phone Number](/docs/plugins/phone-number) Plugin will allow you to add phone number authentication to your application. [Username](/docs/plugins/username) Plugin will allow you to add username authentication to your application. ```ts title="auth.ts" import { Pool } from "pg"; import { betterAuth } from "better-auth"; import { admin, twoFactor, phoneNumber, username } from "better-auth/plugins"; export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL }), emailAndPassword: { enabled: true, }, socialProviders: { github: { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, } }, plugins: [admin(), twoFactor(), phoneNumber(), username()], // [!code highlight] }) ``` ### Generate Schema If you're using a custom database adapter, generate the schema: ```sh npx @better-auth/cli generate ``` or if you're using the default adapter, you can use the following command: ```sh npx @better-auth/cli migrate ``` ### Export Clerk Users Go to the Clerk dashboard and export the users. Check how to do it [here](https://clerk.com/docs/deployments/exporting-users#export-your-users-data-from-the-clerk-dashboard). It will download a CSV file with the users data. You need to save it as `exported_users.csv` and put it in the root of your project. ### Create the migration script Create a new file called `migrate-clerk.ts` in the `scripts` folder and add the following code: ```ts title="scripts/migrate-clerk.ts" import { generateRandomString, symmetricEncrypt } from "better-auth/crypto"; import { auth } from "@/lib/auth"; // import your auth instance function getCSVData(csv: string) { const lines = csv.split('\n').filter(line => line.trim()); const headers = lines[0]?.split(',').map(header => header.trim()) || []; const jsonData = lines.slice(1).map(line => { const values = line.split(',').map(value => value.trim()); return headers.reduce((obj, header, index) => { obj[header] = values[index] || ''; return obj; }, {} as Record); }); return jsonData as Array<{ id: string; first_name: string; last_name: string; username: string; primary_email_address: string; primary_phone_number: string; verified_email_addresses: string; unverified_email_addresses: string; verified_phone_numbers: string; unverified_phone_numbers: string; totp_secret: string; password_digest: string; password_hasher: string; }>; } const exportedUserCSV = await Bun.file("exported_users.csv").text(); // this is the file you downloaded from Clerk async function getClerkUsers(totalUsers: number) { const clerkUsers: { id: string; first_name: string; last_name: string; username: string; image_url: string; password_enabled: boolean; two_factor_enabled: boolean; totp_enabled: boolean; backup_code_enabled: boolean; banned: boolean; locked: boolean; lockout_expires_in_seconds: number; created_at: number; updated_at: number; external_accounts: { id: string; provider: string; identification_id: string; provider_user_id: string; approved_scopes: string; email_address: string; first_name: string; last_name: string; image_url: string; created_at: number; updated_at: number; }[] }[] = []; for (let i = 0; i < totalUsers; i += 500) { const response = await fetch(`https://api.clerk.com/v1/users?offset=${i}&limit=${500}`, { headers: { 'Authorization': `Bearer ${process.env.CLERK_SECRET_KEY}` } }); if (!response.ok) { throw new Error(`Failed to fetch users: ${response.statusText}`); } const clerkUsersData = await response.json(); // biome-ignore lint/suspicious/noExplicitAny: clerkUsers.push(...clerkUsersData as any); } return clerkUsers; } export async function generateBackupCodes( secret: string, ) { const key = secret; const backupCodes = Array.from({ length: 10 }) .fill(null) .map(() => generateRandomString(10, "a-z", "0-9", "A-Z")) .map((code) => `${code.slice(0, 5)}-${code.slice(5)}`); const encCodes = await symmetricEncrypt({ data: JSON.stringify(backupCodes), key: key, }); return encCodes } // Helper function to safely convert timestamp to Date function safeDateConversion(timestamp?: number): Date { if (!timestamp) return new Date(); // Convert seconds to milliseconds const date = new Date(timestamp * 1000); // Check if the date is valid if (isNaN(date.getTime())) { console.warn(`Invalid timestamp: ${timestamp}, falling back to current date`); return new Date(); } // Check for unreasonable dates (before 2000 or after 2100) const year = date.getFullYear(); if (year < 2000 || year > 2100) { console.warn(`Suspicious date year: ${year}, falling back to current date`); return new Date(); } return date; } async function migrateFromClerk() { const jsonData = getCSVData(exportedUserCSV); const clerkUsers = await getClerkUsers(jsonData.length); const ctx = await auth.$context const isAdminEnabled = ctx.options?.plugins?.find(plugin => plugin.id === "admin"); const isTwoFactorEnabled = ctx.options?.plugins?.find(plugin => plugin.id === "two-factor"); const isUsernameEnabled = ctx.options?.plugins?.find(plugin => plugin.id === "username"); const isPhoneNumberEnabled = ctx.options?.plugins?.find(plugin => plugin.id === "phone-number"); for (const user of jsonData) { const { id, first_name, last_name, username, primary_email_address, primary_phone_number, verified_email_addresses, unverified_email_addresses, verified_phone_numbers, unverified_phone_numbers, totp_secret, password_digest, password_hasher } = user; const clerkUser = clerkUsers.find(clerkUser => clerkUser?.id === id); // create user const createdUser = await ctx.adapter.create<{ id: string; }>({ model: "user", data: { id, email: primary_email_address, emailVerified: verified_email_addresses.length > 0, name: `${first_name} ${last_name}`, image: clerkUser?.image_url, createdAt: safeDateConversion(clerkUser?.created_at), updatedAt: safeDateConversion(clerkUser?.updated_at), // # Two Factor (if you enabled two factor plugin) ...(isTwoFactorEnabled ? { twoFactorEnabled: clerkUser?.two_factor_enabled } : {}), // # Admin (if you enabled admin plugin) ...(isAdminEnabled ? { banned: clerkUser?.banned, banExpiresAt: clerkUser?.lockout_expires_in_seconds, role: "user" } : {}), // # Username (if you enabled username plugin) ...(isUsernameEnabled ? { username: username, } : {}), // # Phone Number (if you enabled phone number plugin) ...(isPhoneNumberEnabled ? { phoneNumber: primary_phone_number, phoneNumberVerified: verified_phone_numbers.length > 0, } : {}), }, forceAllowId: true }).catch(async e => { return await ctx.adapter.findOne<{ id: string; }>({ model: "user", where: [{ field: "id", value: id }] }) }) // create external account const externalAccounts = clerkUser?.external_accounts; if (externalAccounts) { for (const externalAccount of externalAccounts) { const { id, provider, identification_id, provider_user_id, approved_scopes, email_address, first_name, last_name, image_url, created_at, updated_at } = externalAccount; if (externalAccount.provider === "credential") { await ctx.adapter.create({ model: "account", data: { id, providerId: provider, accountId: externalAccount.provider_user_id, scope: approved_scopes, userId: createdUser?.id, createdAt: safeDateConversion(created_at), updatedAt: safeDateConversion(updated_at), password: password_digest, } }) } else { await ctx.adapter.create({ model: "account", data: { id, providerId: provider.replace("oauth_", ""), accountId: externalAccount.provider_user_id, scope: approved_scopes, userId: createdUser?.id, createdAt: safeDateConversion(created_at), updatedAt: safeDateConversion(updated_at), }, forceAllowId: true }) } } } //two factor if (isTwoFactorEnabled) { await ctx.adapter.create({ model: "twoFactor", data: { userId: createdUser?.id, secret: totp_secret, backupCodes: await generateBackupCodes(totp_secret) } }) } } } migrateFromClerk() .then(() => { console.log('Migration completed'); process.exit(0); }) .catch((error) => { console.error('Migration failed:', error); process.exit(1); }); ``` Make sure to replace the `process.env.CLERK_SECRET_KEY` with your own Clerk secret key. Feel free to customize the script to your needs. ### Run the migration Run the migration: ```sh bun run script/migrate-clerk.ts # you can use any thing you like to run the script ``` Make sure to: 1. Test the migration in a development environment first 2. Monitor the migration process for any errors 3. Verify the migrated data in Better Auth before proceeding 4. Keep Clerk installed and configured until the migration is complete ### Verify the migration After running the migration, verify that all users have been properly migrated by checking the database. ### Update your components Now that the data is migrated, you can start updating your components to use Better Auth. Here's an example for the sign-in component: ```tsx title="components/auth/sign-in.tsx" import { authClient } from "better-auth/client"; export const SignIn = () => { const handleSignIn = async () => { const { data, error } = await authClient.signIn.email({ email: "user@example.com", password: "password", }); if (error) { console.error(error); return; } // Handle successful sign in }; return (
); }; ```
### Update the middleware Replace your Clerk middleware with Better Auth's middleware: ```ts title="middleware.ts" import { NextRequest, NextResponse } from "next/server"; import { getSessionCookie } from "better-auth/cookies"; export async function middleware(request: NextRequest) { const sessionCookie = getSessionCookie(request); const { pathname } = request.nextUrl; if (sessionCookie && ["/login", "/signup"].includes(pathname)) { return NextResponse.redirect(new URL("/dashboard", request.url)); } if (!sessionCookie && pathname.startsWith("/dashboard")) { return NextResponse.redirect(new URL("/login", request.url)); } return NextResponse.next(); } export const config = { matcher: ["/dashboard", "/login", "/signup"], }; ``` ### Remove Clerk Dependencies Once you've verified that everything is working correctly with Better Auth, you can remove Clerk: ```bash title="Remove Clerk" pnpm remove @clerk/nextjs @clerk/themes @clerk/types ```
## Additional Resources [Goodbye Clerk, Hello Better Auth – Full Migration Guide!](https://www.youtube.com/watch?v=Za_QihbDSuk) ## Wrapping Up Congratulations! You've successfully migrated from Clerk to Better Auth. Better Auth offers greater flexibility and more features—be sure to explore the [documentation](/docs) to unlock its full potential. # guides: Create a Database Adapter URL: /docs/guides/create-a-db-adapter Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/guides/create-a-db-adapter.mdx Learn how to create a custom database adapter for Better-Auth *** title: Create a Database Adapter description: Learn how to create a custom database adapter for Better-Auth -------------------------------------------------------------------------- Learn how to create a custom database adapter for Better-Auth using `createAdapter`. Our `createAdapter` function is designed to be very flexible, and we've done our best to make it easy to understand and use. Our hope is to allow you to focus on writing database logic, and not have to worry about how the adapter is working with Better-Auth. Anything from custom schema configurations, custom ID generation, safe JSON parsing, and more is handled by the `createAdapter` function. All you need to do is provide the database logic, and the `createAdapter` function will handle the rest. ## Quick Start ### Get things ready 1. Import `createAdapter`. 2. Create `CustomAdapterConfig` interface that represents your adapter config options. 3. Create the adapter! ```ts import { createAdapter, type AdapterDebugLogs } from "better-auth/adapters"; // Your custom adapter config options interface CustomAdapterConfig { /** * Helps you debug issues with the adapter. */ debugLogs?: AdapterDebugLogs; /** * If the table names in the schema are plural. */ usePlural?: boolean; } export const myAdapter = (config: CustomAdapterConfig = {}) => createAdapter({ // ... }); ``` ### Configure the adapter The `config` object is mostly used to provide information about the adapter to Better-Auth. We try to minimize the amount of code you need to write in your adapter functions, and these `config` options are used to help us do that. ```ts // ... export const myAdapter = (config: CustomAdapterConfig = {}) => createAdapter({ config: { adapterId: "custom-adapter", // A unique identifier for the adapter. adapterName: "Custom Adapter", // The name of the adapter. usePlural: config.usePlural ?? false, // Whether the table names in the schema are plural. debugLogs: config.debugLogs ?? false, // Whether to enable debug logs. supportsJSON: false, // Whether the database supports JSON. (Default: false) supportsDates: true, // Whether the database supports dates. (Default: true) supportsBooleans: true, // Whether the database supports booleans. (Default: true) supportsNumericIds: true, // Whether the database supports auto-incrementing numeric IDs. (Default: true) }, // ... }); ``` ### Create the adapter The `adapter` function is where you write the code that interacts with your database. ```ts // ... export const myAdapter = (config: CustomAdapterConfig = {}) => createAdapter({ config: { // ... }, adapter: ({}) => { return { create: async ({ data, model, select }) => { // ... }, update: async ({ data, model, select }) => { // ... }, updateMany: async ({ data, model, select }) => { // ... }, delete: async ({ data, model, select }) => { // ... }, // ... }; }, }); ``` Learn more about the `adapter` here [here](/docs/concepts/database#adapters). ## Adapter The `adapter` function is where you write the code that interacts with your database. If you haven't already, check out the `options` object in the [config section](#config), as it can be useful for your adapter. Before we get into the adapter function, let's go over the parameters that are available to you. * `options`: The Better Auth options. * `schema`: The schema from the user's Better Auth instance. * `debugLog`: The debug log function. * `getField`: The get field function. * `getDefaultModelName`: The get default model name function. * `getDefaultFieldName`: The get default field name function. * `getFieldAttributes`: The get field attributes function. ```ts title="Example" adapter: ({ options, schema, debugLog, getField, getDefaultModelName, getDefaultFieldName, }) => { return { // ... }; }; ``` ### Adapter Methods * All `model` values are already transformed into the correct model name for the database based on the end-user's schema configuration. * This also means that if you need access to the `schema` version of a given model, you can't use this exact `model` value, you'll need to use the `getDefaultModelName` function provided in the options to convert the `model` to the `schema` version. * We will automatically fill in any missing fields you return based on the user's `schema` configuration. * Any method that includes a `select` parameter, is only for the purpose of getting data from your database more efficiently. You do not need to worry about only returning what the `select` parameter states, as we will handle that for you. ### `create` method The `create` method is used to create a new record in the database. Note: If the user has enabled the `useNumberId` option, or if `generateId` is `false` in the user's Better Auth config, then it's expected that the `id` is provided in the `data` object. Otherwise, the `id` will be automatically generated. Additionally, it's possible to pass `forceAllowId` as a parameter to the `create` method, which allows `id` to be provided in the `data` object. We handle `forceAllowId` internally, so you don't need to worry about it. parameters: * `model`: The model/table name that new data will be inserted into. * `data`: The data to insert into the database. * `select`: An array of fields to return from the database. Make sure to return the data that is inserted into the database. ```ts title="Example" create: async ({ model, data, select }) => { // Example of inserting data into the database. return await db.insert(model).values(data); }; ``` ### `update` method The `update` method is used to update a record in the database. parameters: * `model`: The model/table name that the record will be updated in. * `where`: The `where` clause to update the record by. * `update`: The data to update the record with. Make sure to return the data in the row which is updated. This includes any fields that were not updated. ```ts title="Example" update: async ({ model, where, update }) => { // Example of updating data in the database. return await db.update(model).set(update).where(where); }; ``` ### `updateMany` method The `updateMany` method is used to update multiple records in the database. parameters: * `model`: The model/table name that the records will be updated in. * `where`: The `where` clause to update the records by. * `update`: The data to update the records with. Make sure to return the number of records that were updated. ```ts title="Example" updateMany: async ({ model, where, update }) => { // Example of updating multiple records in the database. return await db.update(model).set(update).where(where); }; ``` ### `delete` method The `delete` method is used to delete a record from the database. parameters: * `model`: The model/table name that the record will be deleted from. * `where`: The `where` clause to delete the record by. ```ts title="Example" delete: async ({ model, where }) => { // Example of deleting a record from the database. await db.delete(model).where(where); } ``` ### `deleteMany` method The `deleteMany` method is used to delete multiple records from the database. parameters: * `model`: The model/table name that the records will be deleted from. * `where`: The `where` clause to delete the records by. Make sure to return the number of records that were deleted. ```ts title="Example" deleteMany: async ({ model, where }) => { // Example of deleting multiple records from the database. return await db.delete(model).where(where); }; ``` ### `findOne` method The `findOne` method is used to find a single record in the database. parameters: * `model`: The model/table name that the record will be found in. * `where`: The `where` clause to find the record by. * `select`: The `select` clause to return. Make sure to return the data that is found in the database. ```ts title="Example" findOne: async ({ model, where, select }) => { // Example of finding a single record in the database. return await db.select().from(model).where(where).limit(1); }; ``` ### `findMany` method The `findMany` method is used to find multiple records in the database. parameters: * `model`: The model/table name that the records will be found in. * `where`: The `where` clause to find the records by. * `limit`: The limit of records to return. * `sortBy`: The `sortBy` clause to sort the records by. * `offset`: The offset of records to return. Make sure to return the array of data that is found in the database. ```ts title="Example" findMany: async ({ model, where, limit, sortBy, offset }) => { // Example of finding multiple records in the database. return await db .select() .from(model) .where(where) .limit(limit) .offset(offset) .orderBy(sortBy); }; ``` ### `count` method The `count` method is used to count the number of records in the database. parameters: * `model`: The model/table name that the records will be counted in. * `where`: The `where` clause to count the records by. Make sure to return the number of records that were counted. ```ts title="Example" count: async ({ model, where }) => { // Example of counting the number of records in the database. return await db.select().from(model).where(where).count(); }; ``` ### `options` (optional) The `options` object is for any potential config that you got from your custom adapter options. ```ts title="Example" const myAdapter = (config: CustomAdapterConfig) => createAdapter({ config: { // ... }, adapter: ({ options }) => { return { options: config, }; }, }); ``` ### `createSchema` (optional) The `createSchema` method allows the [Better Auth CLI](/docs/concepts/cli) to [generate](/docs/concepts/cli/#generate) a schema for the database. parameters: * `tables`: The tables from the user's Better-Auth instance schema; which is expected to be generated into the schema file. * `file`: The file the user may have passed in to the `generate` command as the expected schema file output path. ```ts title="Example" createSchema: async ({ file, tables }) => { // ... Custom logic to create a schema for the database. }; ``` ## Test your adapter We've provided a test suite that you can use to test your adapter. It requires you to use `vitest`. ```ts title="my-adapter.test.ts" import { expect, test, describe } from "vitest"; import { runAdapterTest } from "better-auth/adapters/test"; import { myAdapter } from "./my-adapter"; describe("My Adapter Tests", async () => { afterAll(async () => { // Run DB cleanup here... }); const adapter = myAdapter({ debugLogs: { // If your adapter config allows passing in debug logs, then pass this here. isRunningAdapterTests: true, // This is our super secret flag to let us know to only log debug logs if a test fails. }, }); await runAdapterTest({ getAdapter: async (betterAuthOptions = {}) => { return adapter(betterAuthOptions); }, }); }); ``` ### Numeric ID tests If your database supports numeric IDs, then you should run this test as well: ```ts title="my-adapter.number-id.test.ts" import { expect, test, describe } from "vitest"; import { runNumberIdAdapterTest } from "better-auth/adapters/test"; import { myAdapter } from "./my-adapter"; describe("My Adapter Numeric ID Tests", async () => { afterAll(async () => { // Run DB cleanup here... }); const adapter = myAdapter({ debugLogs: { // If your adapter config allows passing in debug logs, then pass this here. isRunningAdapterTests: true, // This is our super secret flag to let us know to only log debug logs if a test fails. }, }); await runNumberIdAdapterTest({ getAdapter: async (betterAuthOptions = {}) => { return adapter(betterAuthOptions); }, }); }); ``` ## Config The `config` object is used to provide information about the adapter to Better-Auth. We **highly recommend** going through and reading each provided option below, as it will help you understand how to properly configure your adapter. ### Required Config ### `adapterId` A unique identifier for the adapter. ### `adapterName` The name of the adapter. ### Optional Config ### `supportsNumericIds` Whether the database supports numeric IDs. If this is set to `false` and the user's config has enabled `useNumberId`, then we will throw an error. ### `supportsJSON` Whether the database supports JSON. If the database doesn't support JSON, we will use a `string` to save the JSON data.And when we retrieve the data, we will safely parse the `string` back into a JSON object. ### `supportsDates` Whether the database supports dates. If the database doesn't support dates, we will use a `string` to save the date. (ISO string) When we retrieve the data, we will safely parse the `string` back into a `Date` object. ### `supportsBooleans` Whether the database supports booleans. If the database doesn't support booleans, we will use a `0` or `1` to save the boolean value. When we retrieve the data, we will safely parse the `0` or `1` back into a boolean value. ### `usePlural` Whether the table names in the schema are plural. This is often defined by the user, and passed down through your custom adapter options. If you do not intend to allow the user to customize the table names, you can ignore this option, or set this to `false`. ```ts title="Example" const adapter = myAdapter({ // This value then gets passed into the `usePlural` // option in the createAdapter `config` object. usePlural: true, }); ``` ### `transaction` Whether the adapter supports transactions. If `false`, operations run sequentially; otherwise provide a function that executes a callback with a `TransactionAdapter`. If your database does not support transactions, the error handling and rollback will not be as robust. We recommend using a database that supports transactions for better data integrity. ### `debugLogs` Used to enable debug logs for the adapter. You can pass in a boolean, or an object with the following keys: `create`, `update`, `updateMany`, `findOne`, `findMany`, `delete`, `deleteMany`, `count`. If any of the keys are `true`, the debug logs will be enabled for that method. ```ts title="Example" // Will log debug logs for all methods. const adapter = myAdapter({ debugLogs: true, }); ``` ```ts title="Example" // Will only log debug logs for the `create` and `update` methods. const adapter = myAdapter({ debugLogs: { create: true, update: true, }, }); ``` ### `disableIdGeneration` Whether to disable ID generation. If this is set to `true`, then the user's `generateId` option will be ignored. ### `customIdGenerator` If your database only supports a specific custom ID generation, then you can use this option to generate your own IDs. ### `mapKeysTransformInput` If your database uses a different key name for a given situation, you can use this option to map the keys. This is useful for databases that expect a different key name for a given situation. For example, MongoDB uses `_id` while in Better-Auth we use `id`. Each key in the returned object represents the old key to replace. The value represents the new key. This can be a partial object that only transforms some keys. ```ts title="Example" mapKeysTransformInput: () => { return { id: "_id", // We want to replace `id` to `_id` to save into MongoDB }; }, ``` ### `mapKeysTransformOutput` If your database uses a different key name for a given situation, you can use this option to map the keys. This is useful for databases that use a different key name for a given situation. For example, MongoDB uses `_id` while in Better-Auth we use `id`. Each key in the returned object represents the old key to replace. The value represents the new key. This can be a partial object that only transforms some keys. ```ts title="Example" mapKeysTransformOutput: () => { return { _id: "id", // We want to replace `_id` (from MongoDB) to `id` (for Better-Auth) }; }, ``` ### `customTransformInput` If you need to transform the input data before it is saved to the database, you can use this option to transform the data. If you're using `supportsJSON`, `supportsDates`, or `supportsBooleans`, then the transformations will be applied before your `customTransformInput` function is called. The `customTransformInput` function receives the following arguments: * `data`: The data to transform. * `field`: The field that is being transformed. * `fieldAttributes`: The field attributes of the field that is being transformed. * `select`: The `select` values which the query expects to return. * `model`: The model that is being transformed. * `schema`: The schema that is being transformed. * `options`: Better Auth options. The `customTransformInput` function runs at every key in the data object of a given action. ```ts title="Example" customTransformInput: ({ field, data }) => { if (field === "id") { return "123"; // Force the ID to be "123" } return data; }; ``` ### `customTransformOutput` If you need to transform the output data before it is returned to the user, you can use this option to transform the data. The `customTransformOutput` function is used to transform the output data. Similar to the `customTransformInput` function, it runs at every key in the data object of a given action, but it runs after the data is retrieved from the database. ```ts title="Example" customTransformOutput: ({ field, data }) => { if (field === "name") { return "Bob"; // Force the name to be "Bob" } return data; }; ``` ```ts const some_data = await adapter.create({ model: "user", data: { name: "John", }, }); // The name will be "Bob" console.log(some_data.name); ``` # guides: Migrating from NextAuth.js to Better Auth URL: /docs/guides/next-auth-migration-guide Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/guides/next-auth-migration-guide.mdx A step-by-step guide to transitioning from NextAuth.js to Better Auth. *** title: Migrating from NextAuth.js to Better Auth description: A step-by-step guide to transitioning from NextAuth.js to Better Auth. ----------------------------------------------------------------------------------- In this guide, we’ll walk through the steps to migrate a project from [NextAuth.js](https://authjs.dev/) to Better Auth, ensuring no loss of data or functionality. While this guide focuses on Next.js, it can be adapted for other frameworks as well. *** ## Before You Begin Before starting the migration process, set up Better Auth in your project. Follow the [installation guide](/docs/installation) to get started. *** ### Mapping Existing Columns Instead of altering your existing database column names, you can map them to match Better Auth's expected structure. This allows you to retain your current database schema. #### User Schema Map the following fields in the user schema: * (next-auth v4) `emailVerified`: datetime → boolean #### Session Schema Map the following fields in the session schema: * `expires` → `expiresAt` * `sessionToken` → `token` * (next-auth v4) add `createdAt` with datetime type * (next-auth v4) add `updatedAt` with datetime type ```typescript title="auth.ts" export const auth = betterAuth({ // Other configs session: { fields: { expiresAt: "expires", // Map your existing `expires` field to Better Auth's `expiresAt` token: "sessionToken" // Map your existing `sessionToken` field to Better Auth's `token` } }, }); ``` Make sure to have `createdAt` and `updatedAt` fields on your session schema. #### Account Schema Map these fields in the account schema: * (next-auth v4) `provider` → `providerId` * `providerAccountId` → `accountId` * `refresh_token` → `refreshToken` * `access_token` → `accessToken` * (next-auth v3) `access_token_expires` → `accessTokenExpiresAt` and int → datetime * (next-auth v4) `expires_at` → `accessTokenExpiresAt` and int → datetime * `id_token` → `idToken` * (next-auth v4) add `createdAt` with datetime type * (next-auth v4) add `updatedAt` with datetime type Remove the `session_state`, `type`, and `token_type` fields, as they are not required by Better Auth. ```typescript title="auth.ts" export const auth = betterAuth({ // Other configs account: { fields: { accountId: "providerAccountId", refreshToken: "refresh_token", accessToken: "access_token", accessTokenExpiresAt: "access_token_expires", idToken: "id_token", } }, }); ``` **Note:** If you use ORM adapters, you can map these fields in your schema file. **Example with Prisma:** ```prisma title="schema.prisma" model Session { id String @id @default(cuid()) expiresAt DateTime @map("expires") // Map your existing `expires` field to Better Auth's `expiresAt` token String @map("sessionToken") // Map your existing `sessionToken` field to Better Auth's `token` userId String user User @relation(fields: [userId], references: [id]) } ``` Make sure to have `createdAt` and `updatedAt` fields on your account schema. ### Update the Route Handler In the `app/api/auth` folder, rename the `[...nextauth]` file to `[...all]` to avoid confusion. Then, update the `route.ts` file as follows: ```typescript title="app/api/auth/[...all]/route.ts" import { toNextJsHandler } from "better-auth/next-js"; import { auth } from "~/server/auth"; export const { POST, GET } = toNextJsHandler(auth); ``` ### Update the Client Create a file named `auth-client.ts` in the `lib` folder. Add the following code: ```typescript title="auth-client.ts" import { createAuthClient } from "better-auth/react"; export const authClient = createAuthClient({ baseURL: process.env.BASE_URL! // Optional if the API base URL matches the frontend }); export const { signIn, signOut, useSession } = authClient; ``` #### Social Login Functions Update your social login functions to use Better Auth. For example, for Discord: ```typescript import { signIn } from "~/lib/auth-client"; export const signInDiscord = async () => { const data = await signIn.social({ provider: "discord" }); return data; }; ``` #### Update `useSession` Calls Replace `useSession` calls with Better Auth’s version. Example: ```typescript title="Profile.tsx" import { useSession } from "~/lib/auth-client"; export const Profile = () => { const { data } = useSession(); return (
                    {JSON.stringify(data, null, 2)}
                
); }; ```
### Server-Side Session Handling Use the `auth` instance to get session data on the server: ```typescript title="actions.ts" "use server"; import { auth } from "~/server/auth"; import { headers } from "next/headers"; export const protectedAction = async () => { const session = await auth.api.getSession({ headers: await headers(), }); }; ``` ### Middleware To protect routes with middleware, refer to the [Next.js middleware guide](/docs/integrations/next#middleware).
## Wrapping Up Congratulations! You’ve successfully migrated from NextAuth.js to Better Auth. For a complete implementation with multiple authentication methods, check out the [demo repository](https://github.com/Bekacru/t3-app-better-auth). Better Auth offers greater flexibility and more features—be sure to explore the [documentation](/docs) to unlock its full potential. # guides: Optimizing for Performance URL: /docs/guides/optimizing-for-performance Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/guides/optimizing-for-performance.mdx A guide to optimizing your Better Auth application for performance. *** title: Optimizing for Performance description: A guide to optimizing your Better Auth application for performance. -------------------------------------------------------------------------------- In this guide, we’ll go over some of the ways you can optimize your application for a more performant Better Auth app. ## Caching Caching is a powerful technique that can significantly improve the performance of your Better Auth application by reducing the number of database queries and speeding up response times. ### Cookie Cache Calling your database every time `useSession` or `getSession` is invoked isn’t ideal, especially if sessions don’t change frequently. Cookie caching handles this by storing session data in a short-lived, signed cookie similar to how JWT access tokens are used with refresh tokens. To turn on cookie caching, just set `session.cookieCache` in your auth config: ```ts title="auth.ts" import { betterAuth } from "better-auth"; export const auth = betterAuth({ session: { cookieCache: { enabled: true, maxAge: 5 * 60, // Cache duration in seconds }, }, }); ``` Read more about [cookie caching](/docs/concepts/session-management#cookie-cache). ### Framework Caching Here are examples of how you can do caching in different frameworks and environments: Since Next v15, we can use the `"use cache"` directive to cache the response of a server function. ```ts export async function getUsers() { 'use cache' // [!code highlight] const { users } = await auth.api.listUsers(); return users } ``` Learn more about NextJS use cache directive here. In Remix, you can use the `cache` option in the `loader` function to cache responses on the server. Here’s an example: ```ts import { json } from '@remix-run/node'; export const loader = async () => { const { users } = await auth.api.listUsers(); return json(users, { headers: { 'Cache-Control': 'max-age=3600', // Cache for 1 hour }, }); }; ``` You can read a nice guide on Loader vs Route Cache Headers in Remix here. In SolidStart, you can use the `query` function to cache data. Here’s an example: ```tsx const getUsers = query( async () => (await auth.api.listUsers()).users, "getUsers" ); ``` Learn more about SolidStart `query` function here. With React Query you can use the `useQuery` hook to cache data. Here’s an example: ```ts import { useQuery } from '@tanstack/react-query'; const fetchUsers = async () => { const { users } = await auth.api.listUsers(); return users; }; export default function Users() { const { data: users, isLoading } = useQuery('users', fetchUsers, { staleTime: 1000 * 60 * 15, // Cache for 15 minutes }); if (isLoading) return
Loading...
; return (
    {users.map(user => (
  • {user.name}
  • ))}
); } ``` Learn more about React Query use cache directive here.
## SSR Optimizations If you're using a framework that supports server-side rendering, it's usually best to pre-fetch the user session on the server and use it as a fallback on the client. ```ts const session = await auth.api.getSession({ headers: await headers(), }); //then pass the session to the client ``` ## Database optimizations Optimizing database performance is essential to get the best out of Better Auth. #### Recommended fields to index | Table | Fields | Plugin | | ------------- | -------------------------- | ------------ | | users | `email` | | | accounts | `userId` | | | sessions | `userId`, `token` | | | verifications | `identifier` | | | invitations | `email`, `organizationId` | organization | | members | `userId`, `organizationId` | organization | | organizations | `slug` | organization | | passkey | `userId` | passkey | | twoFactor | `secret` | twoFactor | We intend to add indexing support in our schema generation tool in the future. # guides: Migrating from Supabase Auth to Better Auth URL: /docs/guides/supabase-migration-guide Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/guides/supabase-migration-guide.mdx A step-by-step guide to transitioning from Supabase Auth to Better Auth. *** title: Migrating from Supabase Auth to Better Auth description: A step-by-step guide to transitioning from Supabase Auth to Better Auth. ------------------------------------------------------------------------------------- In this guide, we'll walk through the steps to migrate a project from Supabase Auth to Better Auth. This migration will invalidate all active sessions. While this guide doesn't currently cover migrating two-factor (2FA) or Row Level Security (RLS) configurations, both should be possible with additional steps. ## Before You Begin Before starting the migration process, set up Better Auth in your project. Follow the [installation guide](/docs/installation) to get started. ### Connect to your database You'll need to connect to your database to migrate the users and accounts. Copy your `DATABASE_URL` from your Supabase project and use it to connect to your database. And for this example, we'll need to install `pg` to connect to the database. npm pnpm yarn bun ```bash npm install pg ``` ```bash pnpm add pg ``` ```bash yarn add pg ``` ```bash bun add pg ``` And then you can use the following code to connect to your database. ```ts title="auth.ts" import { Pool } from "pg"; export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL }), }) ``` ### Enable Email and Password (Optional) Enable the email and password in your auth config. ```ts title="auth.ts" import { admin, anonymous } from "better-auth/plugins"; export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL }), emailVerification: { sendEmailVerification: async(user)=>{ // send email verification email // implement your own logic here } }, emailAndPassword: { // [!code highlight] enabled: true, // [!code highlight] } // [!code highlight] }) ``` ### Setup Social Providers (Optional) Add social providers you have enabled in your Supabase project in your auth config. ```ts title="auth.ts" import { admin, anonymous } from "better-auth/plugins"; export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL }), emailAndPassword: { enabled: true, }, 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] }) ``` ### Add admin and anonymous plugins (Optional) Add the [admin](/docs/plugins/admin) and [anonymous](/docs/plugins/anonymous) plugins to your auth config. ```ts title="auth.ts" import { admin, anonymous } from "better-auth/plugins"; export const auth = betterAuth({ database: new Pool({ connectionString: process.env.DATABASE_URL }), emailAndPassword: { enabled: true, }, socialProviders: { github: { clientId: process.env.GITHUB_CLIENT_ID!, clientSecret: process.env.GITHUB_CLIENT_SECRET!, } }, plugins: [admin(), anonymous()], // [!code highlight] }) ``` ### Run the migration Run the migration to create the necessary tables in your database. ```bash title="Terminal" npx @better-auth/cli migrate ``` This will create the following tables in your database: * [`user`](/docs/concepts/database#user) * [`account`](/docs/concepts/database#account) * [`session`](/docs/concepts/database#session) * [`verification`](/docs/concepts/database#verification) This tables will be created on the `public` schema. ### Copy the migration script Now that we have the necessary tables in our database, we can run the migration script to migrate the users and accounts from Supabase to Better Auth. Start by creating a `.ts` file in your project. ```bash title="Terminal" touch migration.ts ``` And then copy and paste the following code into the file. ```ts title="migration.ts" import { Pool } from "pg"; import { auth } from "./auth"; import { User as SupabaseUser } from "@supabase/supabase-js"; type User = SupabaseUser & { is_super_admin: boolean; raw_user_meta_data: { avatar_url: string; }; encrypted_password: string; email_confirmed_at: string; created_at: string; updated_at: string; is_anonymous: boolean; identities: { provider: string; identity_data: { sub: string; email: string; }; created_at: string; updated_at: string; }; }; const migrateFromSupabase = async () => { const ctx = await auth.$context; const db = ctx.options.database as Pool; const users = await db .query(` SELECT u.*, COALESCE( json_agg( i.* ORDER BY i.id ) FILTER (WHERE i.id IS NOT NULL), '[]'::json ) as identities FROM auth.users u LEFT JOIN auth.identities i ON u.id = i.user_id GROUP BY u.id `) .then((res) => res.rows as User[]); for (const user of users) { if (!user.email) { continue; } await ctx.adapter .create({ model: "user", data: { id: user.id, email: user.email, name: user.email, role: user.is_super_admin ? "admin" : user.role, emailVerified: !!user.email_confirmed_at, image: user.raw_user_meta_data.avatar_url, createdAt: new Date(user.created_at), updatedAt: new Date(user.updated_at), isAnonymous: user.is_anonymous, }, }) .catch(() => {}); for (const identity of user.identities) { const existingAccounts = await ctx.internalAdapter.findAccounts(user.id); if (identity.provider === "email") { const hasCredential = existingAccounts.find( (account) => account.providerId === "credential", ); if (!hasCredential) { await ctx.adapter .create({ model: "account", data: { userId: user.id, providerId: "credential", accountId: user.id, password: user.encrypted_password, createdAt: new Date(user.created_at), updatedAt: new Date(user.updated_at), }, }) .catch(() => {}); } } const supportedProviders = Object.keys(ctx.options.socialProviders || {}) if (supportedProviders.includes(identity.provider)) { const hasAccount = existingAccounts.find( (account) => account.providerId === identity.provider, ); if (!hasAccount) { await ctx.adapter.create({ model: "account", data: { userId: user.id, providerId: identity.provider, accountId: identity.identity_data?.sub, createdAt: new Date(identity.created_at ?? user.created_at), updatedAt: new Date(identity.updated_at ?? user.updated_at), }, }); } } } } }; migrateFromSupabase(); ``` ### Customize the migration script (Optional) * `name`: the migration script will use the user's email as the name. You might want to customize it if you have the user display name in your database. * `socialProviderList`: the migration script will use the social providers you have enabled in your auth config. You might want to customize it if you have additional social providers that you haven't enabled in your auth config. * `role`: remove `role` if you're not using the `admin` plugin * `isAnonymous`: remove `isAnonymous` if you're not using the `anonymous` plugin. * update other tables that reference the `users` table to use the `id` field. ### Run the migration script Run the migration script to migrate the users and accounts from Supabase to Better Auth. ```bash title="Terminal" bun migration.ts # or use node, ts-node, etc. ``` ### Update your code Update your codebase from Supabase auth calls to Better Auth API. Here's a list of the Supabase auth API calls and their Better Auth counterparts. * `supabase.auth.signUp` -> `authClient.signUp.email` * `supabase.auth.signInWithPassword` -> `authClient.signIn.email` * `supabase.auth.signInWithOAuth` -> `authClient.signIn.social` * `supabase.auth.signInAnonymously` -> `authClient.signIn.anonymous` * `supabase.auth.signOut` -> `authClient.signOut` * `supabase.auth.getSession` -> `authClient.getSession` - you can also use `authClient.useSession` for reactive state Learn more: * [Basic Usage](/docs/basic-usage): Learn how to use the auth client to sign up, sign in, and sign out. * [Email and Password](/docs/authentication/email-and-password): Learn how to add email and password authentication to your project. * [Anonymous](/docs/plugins/anonymous): Learn how to add anonymous authentication to your project. * [Admin](/docs/plugins/admin): Learn how to add admin authentication to your project. * [Email OTP](/docs/authentication/email-otp): Learn how to add email OTP authentication to your project. * [Hooks](/docs/concepts/hooks): Learn how to use the hooks to listen for events. * [Next.js](/docs/integrations/next): Learn how to use the auth client in a Next.js project. ### Middleware To protect routes with middleware, refer to the [Next.js middleware guide](/docs/integrations/next#middleware) or your framework's documentation. ## Wrapping Up Congratulations! You've successfully migrated from Supabase Auth to Better Auth. Better Auth offers greater flexibility and more features—be sure to explore the [documentation](/docs) to unlock its full potential. # guides: Create your first plugin URL: /docs/guides/your-first-plugin Source: https://raw.githubusercontent.com/better-auth/better-auth/refs/heads/main/docs/content/docs/guides/your-first-plugin.mdx A step-by-step guide to creating your first Better Auth plugin. *** title: Create your first plugin description: A step-by-step guide to creating your first Better Auth plugin. ---------------------------------------------------------------------------- In this guide, we’ll walk you through the steps of creating your first Better Auth plugin. This guide assumes you have setup the basics of Better Auth and are ready to create your first plugin. ## Plan your idea Before beginning, you must know what plugin you intend to create. In this guide, we’ll create a **birthday plugin** to keep track of user birth dates. ## Server plugin first Better Auth plugins operate as a pair: a server plugin and a client plugin. The server plugin forms the foundation of your authentication system, while the client plugin provides convenient frontend APIs to interact with your server implementation. You can read more about server/client plugins in our documentation. ### Creating the server plugin Go ahead and find a suitable location to create your birthday plugin folder, with an `index.ts` file within. In the `index.ts` file, we’ll export a function that represents our server plugin. This will be what we will later add to our plugin list in the `auth.ts` file. ```ts title="index.ts" import { createAuthClient } from "better-auth/client"; import type { BetterAuthPlugin } from "better-auth"; export const birthdayPlugin = () => ({ id: "birthdayPlugin", } satisfies BetterAuthPlugin); ``` Although this does nothing, you have technically just made yourself your first plugin, congratulations! 🎉 ### Defining a schema In order to save each user’s birthday data, we must create a schema on top of the `user` model. By creating a schema here, this also allows Better Auth’s CLI to generate the schemas required to update your database. You can learn more about plugin schemas here. ```ts title="index.ts" //... export const birthdayPlugin = () => ({ id: "birthdayPlugin", schema: {// [!code highlight] user: {// [!code highlight] fields: {// [!code highlight] birthday: {// [!code highlight] type: "date", // string, number, boolean, date // [!code highlight] required: true, // if the field should be required on a new record. (default: false) // [!code highlight] unique: false, // if the field should be unique. (default: false) // [!code highlight] references: null // if the field is a reference to another table. (default: null) // [!code highlight] },// [!code highlight] },// [!code highlight] },// [!code highlight] }, } satisfies BetterAuthPlugin); ``` ### Authorization logic For this example guide, we’ll set up authentication logic to check and ensure that the user who signs-up is older than 5. But the same concept could be applied for something like verifying users agreeing to the TOS or anything alike. To do this, we’ll utilize Hooks, which allows us to run code `before` or `after` an action is performed. ```ts title="index.ts" export const birthdayPlugin = () => ({ //... // In our case, we want to write authorization logic, // meaning we want to intercept it `before` hand. hooks: { before: [ { matcher: (context) => /* ... */, handler: createAuthMiddleware(async (ctx) => { //... }), }, ], }, } satisfies BetterAuthPlugin) ``` In our case we want to match any requests going to the signup path: ```ts title="Before hook" { matcher: (context) => context.path.startsWith("/sign-up/email"), //... } ``` And for our logic, we’ll write the following code to check the if user’s birthday makes them above 5 years old. ```ts title="Imports" import { APIError } from "better-auth/api"; import { createAuthMiddleware } from "better-auth/plugins"; ``` ```ts title="Before hook" { //... handler: createAuthMiddleware(async (ctx) => { const { birthday } = ctx.body; if(!(birthday instanceof Date)) { throw new APIError("BAD_REQUEST", { message: "Birthday must be of type Date." }); } const today = new Date(); const fiveYearsAgo = new Date(today.setFullYear(today.getFullYear() - 5)); if(birthday >= fiveYearsAgo) { throw new APIError("BAD_REQUEST", { message: "User must be above 5 years old." }); } return { context: ctx }; }), } ``` **Authorized!** 🔒 We’ve now successfully written code to ensure authorization for users above 5! ## Client Plugin We’re close to the finish line! 🏁 Now that we have created our server plugin, the next step is to develop our client plugin. Since there isn’t much frontend APIs going on for this plugin, there isn’t much to do! First, let’s create our `client.ts` file first: Then, add the following code: ```ts title="client.ts" import { BetterAuthClientPlugin } from "better-auth"; import type { birthdayPlugin } from "./index"; // make sure to import the server plugin as a type // [!code highlight] type BirthdayPlugin = typeof birthdayPlugin; export const birthdayClientPlugin = () => { return { id: "birthdayPlugin", $InferServerPlugin: {} as ReturnType, } satisfies BetterAuthClientPlugin; }; ``` What we’ve done is allow the client plugin to infer the types defined by our schema from the server plugin. And that’s it! This is all it takes for the birthday client plugin. 🎂 ## Initiate your plugin! Both the `client` and `server` plugins are now ready, the last step is to import them to both your `auth-client.ts` and your `server.ts` files respectively to initiate the plugin. ### Server initiation ```ts title="server.ts" import { betterAuth } from "better-auth"; import { birthdayPlugin } from "./birthday-plugin";// [!code highlight] export const auth = betterAuth({ plugins: [ birthdayPlugin(),// [!code highlight] ] }); ``` ### Client initiation ```ts title="auth-client.ts" import { createAuthClient } from "better-auth/client"; import { birthdayClientPlugin } from "./birthday-plugin/client";// [!code highlight] const authClient = createAuthClient({ plugins: [ birthdayClientPlugin()// [!code highlight] ] }); ``` ### Oh yeah, the schemas! Don’t forget to add your `birthday` field to your `user` table model! Or, use the `generate` CLI command: ```bash npx @better-auth/cli@latest generate ``` ## Wrapping Up Congratulations! You’ve successfully created your first ever Better Auth plugin. We highly recommend you visit our plugins documentation to learn more information. If you have a plugin you’d like to share with the community, feel free to let us know through our Discord server, or through a pull-request and we may add it to the community-plugins list! # 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