file: ./content/docs/basic-usage.mdx
meta: {
"title": "Basic Usage",
"description": "Getting started with Better Auth"
}
Better Auth provides built-in authentication support for:
* **Email and password**
* **Social provider (Google, GitHub, Apple, and more)**
But also can easily be extended using plugins, such as: [username](/docs/plugins/username), [magic link](/docs/plugins/magic-link), [passkey](/docs/plugins/passkey), [email-otp](/docs/plugins/email-otp), and more.
## Email & Password
To enable email and password authentication:
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
emailAndPassword: { // [!code highlight]
enabled: true // [!code highlight]
} // [!code highlight]
})
```
### Sign Up
To sign up a user you need to call the client method `signUp.email` with the user's information.
```ts title="sign-up.ts"
import { authClient } from "@/lib/auth-client"; //import the auth client // [!code highlight]
const { data, error } = await authClient.signUp.email({
email, // user email address
password, // user password -> min 8 characters by default
name, // user display name
image, // user image url (optional)
callbackURL: "/dashboard" // a url to redirect to after the user verifies their email (optional)
}, {
onRequest: (ctx) => {
//show loading
},
onSuccess: (ctx) => {
//redirect to the dashboard or sign in page
},
onError: (ctx) => {
// display the error message
alert(ctx.error.message);
},
});
```
By default, the users are automatically signed in after they successfully sign up. To disable this behavior you can set `autoSignIn` to `false`.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
autoSignIn: false //defaults to true // [!code highlight]
},
})
```
### Sign In
To sign a user in, you can use the `signIn.email` function provided by the client.
```ts title="sign-in"
const { data, error } = await authClient.signIn.email({
/**
* The user email
*/
email,
/**
* The user password
*/
password,
/**
* a url to redirect to after the user verifies their email (optional)
*/
callbackURL: "/dashboard",
/**
* remember the user session after the browser is closed.
* @default true
*/
rememberMe: false
}, {
//callbacks
})
```
Always invoke client methods from the client side. Don't call them from the server.
### Server-Side Authentication
To authenticate a user on the server, you can use the `auth.api` methods.
```ts title="server.ts"
import { auth } from "./auth"; // path to your Better Auth server instance
const response = await auth.api.signInEmail({
body: {
email,
password
},
asResponse: true // returns a response object instead of data
});
```
If the server cannot return a response object, you'll need to manually parse and set cookies. But for frameworks like Next.js we provide [a plugin](/docs/integrations/next#server-action-cookies) to handle this automatically
## Social Sign-On
Better Auth supports multiple social providers, including Google, GitHub, Apple, Discord, and more. To use a social provider, you need to configure the ones you need in the `socialProviders` option on your `auth` object.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
socialProviders: { // [!code highlight]
github: { // [!code highlight]
clientId: process.env.GITHUB_CLIENT_ID!, // [!code highlight]
clientSecret: process.env.GITHUB_CLIENT_SECRET!, // [!code highlight]
} // [!code highlight]
}, // [!code highlight]
})
```
### Signin with social providers
To sign in using a social provider you need to call `signIn.social`. It takes an object with the following properties:
```ts title="sign-in.ts"
import { authClient } from "@/lib/auth-client"; //import the auth client // [!code highlight]
await authClient.signIn.social({
/**
* The social provider id
* @example "github", "google", "apple"
*/
provider: "github",
/**
* a url to redirect after the user authenticates with the provider
* @default "/"
*/
callbackURL: "/dashboard",
/**
* a url to redirect if an error occurs during the sign in process
*/
errorCallbackURL: "/error",
/**
* a url to redirect if the user is newly registered
*/
newUserCallbackURL: "/welcome",
/**
* disable the automatic redirect to the provider.
* @default false
*/
disableRedirect: true,
});
```
You can also authenticate using `idToken` or `accessToken` from the social provider instead of redirecting the user to the provider's site. See social providers documentation for more details.
## Signout
To signout a user, you can use the `signOut` function provided by the client.
```ts title="user-card.tsx"
await authClient.signOut();
```
you can pass `fetchOptions` to redirect onSuccess
```ts title="user-card.tsx"
await authClient.signOut({
fetchOptions: {
onSuccess: () => {
router.push("/login"); // redirect to login page
},
},
});
```
## Session
Once a user is signed in, you'll want to access the user session. Better Auth allows you easily to access the session data from the server and client side.
### Client Side
#### Use Session
Better Auth provides a `useSession` hook to easily access session data on the client side. This hook is implemented using nanostore and has support for each supported framework and vanilla client, ensuring that any changes to the session (such as signing out) are immediately reflected in your UI.
```tsx title="user.tsx"
import { authClient } from "@/lib/auth-client" // import the auth client // [!code highlight]
export function User(){
const { // [!code highlight]
data: session, // [!code highlight]
isPending, //loading state // [!code highlight]
error, //error object // [!code highlight]
refetch //refetch the session
} = authClient.useSession() // [!code highlight]
return (
//...
)
}
```
```vue title="index.vue"
{{ session.data }}
```
```svelte title="user.svelte"
{$session.data?.user.email}
```
```ts title="user.svelte"
import { authClient } from "~/lib/auth-client"; //import the auth client
authClient.useSession.subscribe((value)=>{
//do something with the session //
})
```
```tsx title="user.tsx"
import { authClient } from "~/lib/auth-client"; // [!code highlight]
export default function Home() {
const session = authClient.useSession() // [!code highlight]
return (
{JSON.stringify(session(), null, 2)}
);
}
```
#### Get Session
If you prefer not to use the hook, you can use the `getSession` method provided by the client.
```ts title="user.tsx"
import { authClient } from "@/lib/auth-client" // import the auth client // [!code highlight]
const { data: session, error } = await authClient.getSession()
```
You can also use it with client-side data-fetching libraries like [TanStack Query](https://tanstack.com/query/latest).
### Server Side
The server provides a `session` object that you can use to access the session data. It requires request headers object to be passed to the `getSession` method.
**Example: Using some popular frameworks**
```ts title="server.ts"
import { auth } from "./auth"; // path to your Better Auth server instance
import { headers } from "next/headers";
const session = await auth.api.getSession({
headers: await headers() // you need to pass the headers object.
})
```
```ts title="route.ts"
import { auth } from "lib/auth"; // path to your Better Auth server instance
export async function loader({ request }: LoaderFunctionArgs) {
const session = await auth.api.getSession({
headers: request.headers
})
return json({ session })
}
```
```astro title="index.astro"
---
import { auth } from "./auth";
const session = await auth.api.getSession({
headers: Astro.request.headers,
});
---
```
```ts title="+page.ts"
import { auth } from "./auth";
export async function load({ request }) {
const session = await auth.api.getSession({
headers: request.headers
})
return {
props: {
session
}
}
}
```
```ts title="index.ts"
import { auth } from "./auth";
const app = new Hono();
app.get("/path", async (c) => {
const session = await auth.api.getSession({
headers: c.req.raw.headers
})
});
```
```ts title="server/session.ts"
import { auth } from "~/utils/auth";
export default defineEventHandler((event) => {
const session = await auth.api.getSession({
headers: event.headers,
})
});
```
```ts title="app/routes/api/index.ts"
import { auth } from "./auth";
import { createAPIFileRoute } from "@tanstack/start/api";
export const APIRoute = createAPIFileRoute("/api/$")({
GET: async ({ request }) => {
const session = await auth.api.getSession({
headers: request.headers
})
},
});
```
For more details check [session-management](/docs/concepts/session-management) documentation.
## Using Plugins
One of the unique features of Better Auth is a plugins ecosystem. It allows you to add complex auth related functionality with small lines of code.
Below is an example of how to add two factor authentication using two factor plugin.
### Server Configuration
To add a plugin, you need to import the plugin and pass it to the `plugins` option of the auth instance. For example, to add two factor authentication, you can use the following code:
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins" // [!code highlight]
export const auth = betterAuth({
//...rest of the options
plugins: [ // [!code highlight]
twoFactor() // [!code highlight]
] // [!code highlight]
})
```
now two factor related routes and method will be available on the server.
### Migrate Database
After adding the plugin, you'll need to add the required tables to your database. You can do this by running the `migrate` command, or by using the `generate` command to create the schema and handle the migration manually.
generating the schema:
```bash title="terminal"
npx @better-auth/cli generate
```
using the `migrate` command:
```bash title="terminal"
npx @better-auth/cli migrate
```
If you prefer adding the schema manually, you can check the schema required on the [two factor plugin](/docs/plugins/2fa#schema) documentation.
### Client Configuration
Once we're done with the server, we need to add the plugin to the client. To do this, you need to import the plugin and pass it to the `plugins` option of the auth client. For example, to add two factor authentication, you can use the following code:
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client";
import { twoFactorClient } from "better-auth/client/plugins"; // [!code highlight]
const authClient = createAuthClient({
plugins: [ // [!code highlight]
twoFactorClient({ // [!code highlight]
twoFactorPage: "/two-factor" // the page to redirect if a user need to verify 2nd factor // [!code highlight]
}) // [!code highlight]
] // [!code highlight]
})
```
now two factor related methods will be available on the client.
```ts title="profile.ts"
import { authClient } from "./auth-client"
const enableTwoFactor = async() => {
const data = await authClient.twoFactor.enable({
password // the user password is required
}) // this will enable two factor
}
const disableTwoFactor = async() => {
const data = await authClient.twoFactor.disable({
password // the user password is required
}) // this will disable two factor
}
const signInWith2Factor = async() => {
const data = await authClient.signIn.email({
//...
})
//if the user has two factor enabled, it will redirect to the two factor page
}
const verifyTOTP = async() => {
const data = await authClient.twoFactor.verifyTOTP({
code: "123456", // the code entered by the user
/**
* If the device is trusted, the user won't
* need to pass 2FA again on the same device
*/
trustDevice: true
})
}
```
Next step: See the the two factor plugin documentation.
file: ./content/docs/comparison.mdx
meta: {
"title": "Comparison",
"description": "Comparison of Better Auth versus over other auth libraries and services."
}
>
Comparison is the thief of joy. — Kanye West
Here are non detailed reasons why you may want to use Better Auth over other auth libraries and services.
### vs Other Auth Libraries
* You need more features from your auth library.
* We support almost all frameworks out of the box.
* You need advanced features like two-factor authentication, multi-tenancy, multi-session, admin tools, rate limiting, etc.
* You want to have more control over your auth flow.
* You prefer more comprehensive documentation.
* You like the idea of a plugin system to extend the library.
* You want to use a library that is actively maintained and has a growing community.
### vs Self Hosted Auth Server
* You want to keep your users in your main database (the same database as your app).
* You need more control over your auth flow.
* You don't want the hassle of self-hosting your auth server.
* And there's a high possibility that we already cover the features you need. Additionally, you can always extend it using our plugin system.
### vs Managed Auth Services (Paid)
* You don't want to give up user data to a third party.
* You want a single source of truth for your users.
* $$$ - You don't want to pay for every user.
* You want to keep your users in your own database (the same database as your app).
### vs Roll Your Own Auth
* You don't want to maintain a lot of code that isn't your core business.
* You want to delegate the security to a community of people who probably care more than you do.
* We probably cover most of the features you'll need, and you can always extend it using our plugin system.
file: ./content/docs/installation.mdx
meta: {
"title": "Installation",
"description": "Learn how to configure Better Auth in your project."
}
### Install the Package
Let's start by adding Better Auth to your project:
```bash
npm install better-auth
```
```bash
pnpm add better-auth
```
```bash
yarn add better-auth
```
```bash
bun add better-auth
```
If you're using a separate client and server setup, make sure to install Better Auth in both parts of your project.
### Set Environment Variables
Create a `.env` file in the root of your project and add the following environment variables:
1. **Secret Key**
Random value used by the library for encryption and generating hashes. **You can generate one using the button below** or you can use something like openssl.
```txt title=".env"
BETTER_AUTH_SECRET=
```
2. **Set Base URL**
```txt title=".env"
BETTER_AUTH_URL=http://localhost:3000 #Base URL of your app
```
### Create A Better Auth Instance
Create a file named `auth.ts` in one of these locations:
* Project root
* `lib/` folder
* `utils/` folder
You can also nest any of these folders under `src/`, `app/` or `server/` folder. (e.g. `src/lib/auth.ts`, `app/lib/auth.ts`).
And in this file, import Better Auth and create your auth instance. Make sure to export the auth instance with the variable name `auth` or as a `default` export.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
//...
})
```
### Configure Database
Better Auth requires a database to store user data. You can easily configure Better Auth to use SQLite, PostgreSQL, or MySQL, with Kysely handling queries and migrations for these databases.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import Database from "better-sqlite3";
export const auth = betterAuth({
database: new Database("./sqlite.db"),
})
```
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { Pool } from "pg";
export const auth = betterAuth({
database: new Pool({
// connection options
})
})
```
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { createPool } from "mysql2/promise";
export const auth = betterAuth({
database: createPool({
// connection options
})
})
```
You can also provide any Kysely dialect or a Kysely instance to the `database` option.
**Example with LibsqlDialect:**
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { LibsqlDialect } from "@libsql/kysely-libsql";
const dialect = new LibsqlDialect({
url: process.env.TURSO_DATABASE_URL || "",
authToken: process.env.TURSO_AUTH_TOKEN || "",
})
export const auth = betterAuth({
database: {
dialect,
type: "sqlite"
}
});
```
**Adapters**
If you prefer to use an ORM or if your database is not supported by Kysely, you can use one of the built-in adapters.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: "sqlite", // or "mysql", "postgresql", ...etc
}),
});
```
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/db"; // your drizzle instance
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg", // or "mysql", "sqlite"
})
});
```
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { mongodbAdapter } from "better-auth/adapters/mongodb";
import { client } from "@/db"; // your mongodb client
export const auth = betterAuth({
database: mongodbAdapter(client)
});
```
### Create Database Tables
Better Auth includes a CLI tool to help manage the schema required by the library.
* **Generate**: This command generates an ORM schema or SQL migration file.
If you're using Kysely, you can apply the migration directly with `migrate` command below. Use `generate` only if you plan to apply the migration manually.
```bash title="Terminal"
npx @better-auth/cli generate
```
* **Migrate**: This command creates the required tables directly in the database. (Available only for the built-in Kysely adapter)
```bash title="Terminal"
npx @better-auth/cli migrate
```
see the [CLI documentation](/docs/concepts/cli) for more information.
If you instead want to create the schema manually, you can find the core schema required in the [database section](/docs/concepts/database#core-schema).
### Authentication Methods
Configure the authentication methods you want to use. Better Auth comes with built-in support for email/password, and social sign-on providers.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
//...other options
emailAndPassword: { // [!code highlight]
enabled: true // [!code highlight]
},// [!code highlight]
socialProviders: { // [!code highlight]
github: { // [!code highlight]
clientId: process.env.GITHUB_CLIENT_ID, // [!code highlight]
clientSecret: process.env.GITHUB_CLIENT_SECRET, // [!code highlight]
} // [!code highlight]
}, // [!code highlight]
});
```
You can use even more authentication methods like [passkey](/docs/plugins/passkey), [username](/docs/plugins/username), [magic link](/docs/plugins/magic-link) and more through plugins.
### Mount Handler
To handle api requests, you need to set up a route handler on your server.
Create a new file or route in your framework's designated catch-all route handler. This route should handle requests for the path `/api/auth/*` (unless you've configured a different base path).
Better Auth supports any backend framework with standard Request and Response objects and offers helper functions for popular frameworks.
```ts title="/app/api/auth/[...all]/route.ts"
import { auth } from "@/lib/auth"; // path to your auth file
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth);
```
```ts title="/server/api/auth/[...all].ts"
import { auth } from "~/utils/auth"; // path to your auth file
export default defineEventHandler((event) => {
return auth.handler(toWebRequest(event));
});
```
```ts title="hooks.server.ts"
import { auth } from "$lib/auth"; // path to your auth file
import { svelteKitHandler } from "better-auth/svelte-kit";
export async function handle({ event, resolve }) {
return svelteKitHandler({ event, resolve, auth });
}
```
```ts title="/app/routes/api.auth.$.ts"
import { auth } from '~/lib/auth.server' // Adjust the path as necessary
import type { LoaderFunctionArgs, ActionFunctionArgs } from "@remix-run/node"
export async function loader({ request }: LoaderFunctionArgs) {
return auth.handler(request)
}
export async function action({ request }: ActionFunctionArgs) {
return auth.handler(request)
}
```
```ts title="/routes/api/auth/*all.ts"
import { auth } from "~/lib/auth"; // path to your auth file
import { toSolidStartHandler } from "better-auth/solid-start";
export const { GET, POST } = toSolidStartHandler(auth);
```
```ts title="src/index.ts"
import { Hono } from "hono";
import { auth } from "./auth"; // path to your auth file
import { serve } from "@hono/node-server";
import { cors } from "hono/cors";
const app = new Hono();
app.on(["POST", "GET"], "/api/auth/**", (c) => auth.handler(c.req.raw));
serve(app);
```
```ts title="server.ts"
import express from "express";
import { toNodeHandler } from "better-auth/node";
import { auth } from "./auth";
const app = express();
const port = 8000;
app.all("/api/auth/*", toNodeHandler(auth));
// Mount express json middleware after Better Auth handler
// or only apply it to routes that don't interact with Better Auth
app.use(express.json());
app.listen(port, () => {
console.log(`Better Auth app listening on port ${port}`);
});
```
This also works for any other node server framework like express, fastify, hapi, etc. Note that CommonJS (cjs) isn't supported.
```ts title="/pages/api/auth/[...all].ts"
import type { APIRoute } from "astro";
import { auth } from "@/auth"; // path to your auth file
export const GET: APIRoute = async (ctx) => {
return auth.handler(ctx.request);
};
export const POST: APIRoute = async (ctx) => {
return auth.handler(ctx.request);
};
```
```ts
import { Elysia, Context } from "elysia";
import { auth } from "./auth";
const betterAuthView = (context: Context) => {
const BETTER_AUTH_ACCEPT_METHODS = ["POST", "GET"]
// validate request method
if(BETTER_AUTH_ACCEPT_METHODS.includes(context.request.method)) {
return auth.handler(context.request);
} else {
context.error(405)
}
}
const app = new Elysia().all("/api/auth/*", betterAuthView).listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
```
```ts title="app/routes/api/auth/$.ts"
import { auth } from '~/lib/server/auth'
import { createAPIFileRoute } from '@tanstack/start/api'
export const APIRoute = createAPIFileRoute('/api/auth/$')({
GET: ({ request }) => {
return auth.handler(request)
},
POST: ({ request }) => {
return auth.handler(request)
},
});
```
```ts title="app/api/auth/[..all]+api.ts"
import { auth } from '@/lib/server/auth'; // path to your auth file
const handler = auth.handler;
export { handler as GET, handler as POST };
```
### Create Client Instance
The client-side library helps you interact with the auth server. Better Auth comes with a client for all the popular web frameworks, including vanilla JavaScript.
1. Import `createAuthClient` from the package for your framework (e.g., "better-auth/react" for React).
2. Call the function to create your client.
3. Pass the base url of your auth server. (If the auth server is running on the same domain as your client, you can skip this step.)
If you're using a different base path other than `/api/auth` make sure to pass the whole url including the path. (e.g. `http://localhost:3000/custom-path/auth`)
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/client"
export const authClient = createAuthClient({
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
})
```
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/react"
export const authClient = createAuthClient({
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
})
```
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/vue"
export const authClient = createAuthClient({
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
})
```
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/svelte"
export const authClient = createAuthClient({
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
})
```
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/solid"
export const authClient = createAuthClient({
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
})
```
Tip: You can also export specific methods if you prefer:
```ts
export const { signIn, signUp, useSession } = createAuthClient()
```
### 🎉 That's it!
That's it! You're now ready to use better-auth in your application. Continue to [basic usage](/docs/basic-usage) to learn how to use the auth instance to sign in users.
file: ./content/docs/introduction.mdx
meta: {
"title": "Introduction",
"description": "Introduction to Better Auth."
}
Better Auth is a framework-agnostic authentication and authorization framework for TypeScript. It provides a comprehensive set of features out of the box and includes a plugin ecosystem that simplifies adding advanced functionalities. Whether you need 2FA, multi-tenancy, multi-session support, or even enterprise features like SSO, it lets you focus on building your application instead of reinventing the wheel.
## Why Better Auth?
*Authentication in the TypeScript ecosystem has long been a half-solved problem. Other open-source libraries often require a lot of additional code for anything beyond basic authentication features. Rather than just pushing third-party services as the solution, I believe we can do better as a community—hence, Better Auth*
## Features
Better Auth aims to be the most comprehensive auth library. It provides a wide range of features out of the box and allows you to extend it with plugins. Here are some of the features:
...and much more!
file: ./content/docs/authentication/apple.mdx
meta: {
"title": "Apple",
"description": "Apple provider setup and usage."
}
### Get your OAuth credentials
To use Apple sign in, you need a client ID and client secret. You can get them from the [Apple Developer Portal](https://developer.apple.com/account/resources/authkeys/list).
Apple requires a little harder setup to get a client secret. You can use the guide below to get your client secret.
Creating a client secret
### Configure the provider
To configure the provider, you need to add it to the `socialProviders` option of the auth instance.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
apple: { // [!code highlight]
clientId: process.env.APPLE_CLIENT_ID as string, // [!code highlight]
clientSecret: process.env.APPLE_CLIENT_SECRET as string, // [!code highlight]
// Optional
appBundleIdentifier: process.env.APPLE_APP_BUNDLE_IDENTIFIER as string, // [!code highlight]
}, // [!code highlight]
},
})
```
On native iOS, it doesn't use the service id but the app id (bundle id) as client id, so if using the service id as clientId in signIn.social() with idToken, it throws an error: JWTClaimValidationFailed: unexpected "aud" claim value. So you need to provide the appBundleIdentifier when you want to sign in with Apple using the ID Token.
## Usage
### Sign In with Apple
To sign in with Apple, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
* `provider`: The provider to use. It should be set to `apple`.
```ts title="auth-client.ts" /
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient()
const signIn = async () => {
const data = await authClient.signIn.social({
provider: "apple"
})
}
```
### Sign In with Apple With ID Token
To sign in with Apple using the ID Token, you can use the `signIn.social` function to pass the ID Token.
This is useful when you have the ID Token from Apple on the client-side and want to use it to sign in on the server.
If id token is provided no redirection will happen, and the user will be signed in directly.
```ts title="auth-client.ts"
await authClient.signIn.social({
provider: "apple",
idToken: {
token: // Apple ID Token,
nonce: // Nonce (optional)
accessToken: // Access Token (optional)
}
})
```
file: ./content/docs/authentication/discord.mdx
meta: {
"title": "Discord",
"description": "Discord provider setup and usage."
}
### Get your Discord credentials
To use Discord sign in, you need a client ID and client secret. You can get them from the [Discord Developer Portal](https://discord.com/developers/applications).
Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/discord` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly.
### Configure the provider
To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
discord: { // [!code highlight]
clientId: process.env.DISCORD_CLIENT_ID as string, // [!code highlight]
clientSecret: process.env.DISCORD_CLIENT_SECRET as string, // [!code highlight]
}, // [!code highlight]
},
})
```
### Sign In with Discord
To sign in with Discord, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
* `provider`: The provider to use. It should be set to `discord`.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient()
const signIn = async () => {
const data = await authClient.signIn.social({
provider: "discord"
})
}
```
file: ./content/docs/authentication/dropbox.mdx
meta: {
"title": "Dropbox",
"description": "Dropbox provider setup and usage."
}
### Get your Dropbox credentials
To use Dropbox sign in, you need a client ID and client secret. You can get them from the [Dropbox Developer Portal](https://www.dropbox.com/developers). You can Allow "Implicit Grant & PKCE" for the application in the App Console.
Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/dropbox` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly.
If you need deeper dive into Dropbox Authentication, you can check out the [official documentation](https://developers.dropbox.com/oauth-guide).
### Configure the provider
To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
dropbox: { // [!code highlight]
clientId: process.env.DROPBOX_CLIENT_ID as string, // [!code highlight]
clientSecret: process.env.DROPBOX_CLIENT_SECRET as string, // [!code highlight]
}, // [!code highlight]
},
})
```
### Sign In with Dropbox
To sign in with Dropbox, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
* `provider`: The provider to use. It should be set to `dropbox`.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient()
const signIn = async () => {
const data = await authClient.signIn.social({
provider: "dropbox"
})
}
```
file: ./content/docs/authentication/email-password.mdx
meta: {
"title": "Email & Password",
"description": "Implementing email and password authentication with Better Auth."
}
Email and password authentication is a common method used by many applications. Better Auth provides a built-in email and password authenticator that you can easily integrate into your project.
If you prefer username-based authentication, check out the{" "}
username plugin. It extends the
email and password authenticator with username support.
## Enable Email and Password
To enable email and password authentication, you need to set the `emailAndPassword.enabled` option to `true` in the `auth` configuration.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
emailAndPassword: { // [!code highlight]
enabled: true, // [!code highlight]
}, // [!code highlight]
});
```
If it's not enabled, it'll not allow you to sign in or sign up with email and
password.
## Usage
### Sign Up
To sign a user up, you can use the `signUp.email` function provided by the client. The `signUp` function takes an object with the following properties:
* `email`: The email address of the user.
* `password`: The password of the user. It should be at least 8 characters long and max 32 by default.
* `name`: The name of the user.
* `image`: The image of the user. (optional)
```ts title="auth-client.ts"
const { data, error } = await authClient.signUp.email({
email: "test@example.com",
password: "password1234",
name: "test",
image: "https://example.com/image.png",
});
```
### Sign In
To sign a user in, you can use the `signIn.email` function provided by the client. The `signIn` function takes an object with the following properties:
* `email`: The email address of the user.
* `password`: The password of the user.
* `rememberMe`: If false, the user will be signed out when the browser is closed. (optional) (default: true)
* `callbackURL`: The URL to redirect to after the user signs in. (optional)
```ts title="auth-client.ts"
const { data, error } = await authClient.signIn.email({
email: "test@example.com",
password: "password1234",
});
```
### Sign Out
To sign a user out, you can use the `signOut` function provided by the client.
```ts title="auth-client.ts"
await authClient.signOut();
```
you can pass `fetchOptions` to redirect onSuccess
```ts title="auth-client.ts"
await authClient.signOut({
fetchOptions: {
onSuccess: () => {
router.push("/login"); // redirect to login page
},
},
});
```
### Email Verification
To enable email verification, you need to pass a function that sends a verification email with a link. The `sendVerificationEmail` function takes a data object with the following properties:
* `user`: The user object.
* `url`: The url to send to the user which contains the token.
* `token`: A verification token used to complete the email verification.
and a `request` object as the second parameter.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { sendEmail } from "./email"; // your email sending function
export const auth = betterAuth({
emailVerification: {
sendVerificationEmail: async ( { user, url, token }, request) => {
await sendEmail({
to: user.email,
subject: "Verify your email address",
text: `Click the link to verify your email: ${url}`,
});
},
},
});
```
On the client side you can use `sendVerificationEmail` function to send verification link to user. This will trigger the `sendVerificationEmail` function you provided in the `auth` configuration.
Once the user clicks on the link in the email, if the token is valid, the user will be redirected to the URL provided in the `callbackURL` parameter. If the token is invalid, the user will be redirected to the URL provided in the `callbackURL` parameter with an error message in the query string `?error=invalid_token`.
#### Require Email Verification
If you enable require email verification, users must verify their email before they can log in. And every time a user tries to sign in, sendVerificationEmail is called.
This only works if you have sendVerificationEmail implemented and if the user
is trying to sign in with email and password.
```ts title="auth.ts"
export const auth = betterAuth({
emailAndPassword: {
requireEmailVerification: true,
},
});
```
If a user tries to sign in without verifying their email, you can handle the error and show a message to the user.
```ts title="auth-client.ts"
await authClient.signIn.email(
{
email: "email@example.com",
password: "password",
},
{
onError: (ctx) => {
// Handle the error
if (ctx.error.status === 403) {
alert("Please verify your email address");
}
//you can also show the original error message
alert(ctx.error.message);
},
}
);
```
#### Triggering manually Email Verification
You can trigger the email verification manually by calling the `sendVerificationEmail` function.
```ts
await authClient.sendVerificationEmail({
email: "user@email.com",
callbackURL: "/", // The redirect URL after verification
});
```
### Forget Password
To allow users to reset a password first you need to provide `sendResetPassword` function to the email and password authenticator. The `sendResetPassword` function takes a data object with the following properties:
* `user`: The user object.
* `url`: The url to send to the user which contains the token.
* `token`: A verification token used to complete the password reset.
and a `request` object as the second parameter.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { sendEmail } from "./email"; // your email sending function
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
sendResetPassword: async ({user, url, token}, request) => {
await sendEmail({
to: user.email,
subject: "Reset your password",
text: `Click the link to reset your password: ${url}`,
});
},
},
});
```
Once you configured your server you can call `forgetPassword` function to send reset password link to user. If the user exists, it will trigger the `sendResetPassword` function you provided in the auth config.
It takes an object with the following properties:
* `email`: The email address of the user.
* `redirectTo`: The URL to redirect to after the user clicks on the link in the email. If the token is valid, the user will be redirected to this URL with the token in the query string. If the token is invalid, the user will be redirected to this URL with an error message in the query string `?error=invalid_token`.
```ts title="auth-client.ts"
const { data, error } = await authClient.forgetPassword({
email: "test@example.com",
redirectTo: "/reset-password",
});
```
When a user clicks on the link in the email, they will be redirected to the reset password page. You can add the reset password page to your app. Then you can use `resetPassword` function to reset the password. It takes an object with the following properties:
* `newPassword`: The new password of the user.
```ts title="auth-client.ts"
const token = new URLSearchParams(window.location.search).get("token");
if (!token) {
// Handle the error
}
const { data, error } = await authClient.resetPassword({
newPassword: "password1234",
token,
});
```
### Update password
This only works on server-side, and the following code may change over time.
To set a password, you must hash it first:
```ts
const ctx = await auth.$context;
const hash = await ctx.password.hash("your-new-password");
```
Then, to set the password:
```ts
await ctx.internalAdapter.updatePassword("userId", hash) //(you can also use your orm directly)
```
### Configuration
**Password**
Better Auth stores passwords inside the `account` table with `providerId` set to `credential`.
**Password Hashing**: Better Auth uses `scrypt` to hash passwords. The `scrypt` algorithm is designed to be slow and memory-intensive to make it difficult for attackers to brute force passwords. OWASP recommends using `scrypt` if `argon2id` is not available. We decided to use `scrypt` because it's natively supported by Node.js.
You can pass custom password hashing algorithm by setting `passwordHasher` option in the `auth` configuration.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { scrypt } from "scrypt"
export const auth = betterAuth({
//...rest of the options
emailAndPassword: {
password: {
hash: // your custom password hashing function
verify: // your custom password verification function
}
}
})
```
file: ./content/docs/authentication/facebook.mdx
meta: {
"title": "Facebook",
"description": "Facebook provider setup and usage."
}
### Get your Facebook credentials
To use Facebook sign in, you need a client ID and client Secret. You can get them from the [Facebook Developer Portal](https://developers.facebook.com/).
Select your app, navigate to **App Settings > Basic**, locate the following:
* **App ID**: This is your `clientId`
* **App Secret**: This is your `clientSecret`.
Avoid exposing the `clientSecret` in client-side code (e.g., frontend apps) because it’s sensitive information.
Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/facebook` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly.
### Configure the provider
To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
facebook: { // [!code highlight]
clientId: process.env.FACEBOOK_CLIENT_ID as string, // [!code highlight]
clientSecret: process.env.FACEBOOK_CLIENT_SECRET as string, // [!code highlight]
}, // [!code highlight]
},
})
```
### Sign In with Facebook
To sign in with Facebook, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
* `provider`: The provider to use. It should be set to `facebook`.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/auth-client"
const authClient = createAuthClient()
const signIn = async () => {
const data = await authClient.signIn.social({
provider: "facebook"
})
}
```
## Additional Configuration
### Scopes
By default, Facebook provides basic user information. If you need additional permissions, you can specify scopes in your auth configuration:
```ts title="auth.ts"
export const auth = betterAuth({
socialProviders: {
facebook: {
clientId: process.env.FACEBOOK_CLIENT_ID as string,
clientSecret: process.env.FACEBOOK_CLIENT_ID as string,
scopes: ["email", "public_profile", "user_friends"], // Overwrites permissions
fields: ["user_friends"], // Extending list of fields
},
},
})
```
Additional options:
* `scopes`: Access basic account information (overwrites).
* Default: `"email", "public_profile"`
* `fields`: Extend list of fields to retrieve from the Facebook user profile (assignment).
* Default: `"id", "name", "email", "picture"`
### Sign In with Facebook With ID or Access Token
To sign in with Facebook using the ID Token, you can use the `signIn.social` function to pass the ID Token.
This is useful when you have the ID Token from Facebook on the client-side and want to use it to sign in on the server.
If id token is provided no redirection will happen, and the user will be signed in directly.
For limited login, you need to pass `idToken.token`, for only `accessToken` you need to pass `idToken.accessToken` and `idToken.token` together because of (#1183)\[[https://github.com/better-auth/better-auth/issues/1183](https://github.com/better-auth/better-auth/issues/1183)].
```ts title="auth-client.ts"
const data = await authClient.signIn.social({
provider: "facebook",
idToken: { // [!code highlight]
...(platform === 'ios' ? // [!code highlight]
{ token: idToken } // [!code highlight]
: { token: accessToken, accessToken: accessToken }), // [!code highlight]
},
})
```
For a complete list of available permissions, refer to the [Permissions Reference](https://developers.facebook.com/docs/permissions).
file: ./content/docs/authentication/github.mdx
meta: {
"title": "GitHub",
"description": "GitHub provider setup and usage."
}
### Get your GitHub credentials
To use GitHub sign in, you need a client ID and client secret. You can get them from the [GitHub Developer Portal](https://github.com/settings/developers).
Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/github` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly.
### Configure the provider
To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
github: { // [!code highlight]
clientId: process.env.GITHUB_CLIENT_ID as string, // [!code highlight]
clientSecret: process.env.GITHUB_CLIENT_SECRET as string, // [!code highlight]
}, // [!code highlight]
},
})
```
### Sign In with GitHub
To sign in with GitHub, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
* `provider`: The provider to use. It should be set to `github`.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient()
const signIn = async () => {
const data = await authClient.signIn.social({
provider: "github"
})
}
```
file: ./content/docs/authentication/gitlab.mdx
meta: {
"title": "GitLab",
"description": "GitLab provider setup and usage."
}
### Get your GitLab credentials
To use GitLab sign in, you need a client ID and client secret. [GitLab OAuth documentation](https://docs.gitlab.com/ee/api/oauth2.html).
Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/gitlab` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly.
### Configure the provider
To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
gitlab: { // [!code highlight]
clientId: process.env.GITLAB_CLIENT_ID as string, // [!code highlight]
clientSecret: process.env.GITLAB_CLIENT_SECRET as string, // [!code highlight]
issuer: process.env.GITLAB_ISSUER as string, // [!code highlight]
}, // [!code highlight]
},
})
```
### Sign In with GitLab
To sign in with GitLab, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
* `provider`: The provider to use. It should be set to `gitlab`.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient()
const signIn = async () => {
const data = await authClient.signIn.social({
provider: "gitlab"
})
}
```
file: ./content/docs/authentication/google.mdx
meta: {
"title": "Google",
"description": "Google provider setup and usage."
}
### Get your Google credentials
To use Google as a social provider, you need to get your Google credentials. You can get them by creating a new project in the [Google Cloud Console](https://console.cloud.google.com/apis/dashboard).
In the Google Cloud Console > Credentials > Authorized redirect URIs, make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/google` for local development. For production, make sure to set the redirect URL as your application domain, e.g. `https://example.com/api/auth/callback/google`. If you change the base path of the auth routes, you should update the redirect URL accordingly.
### Configure the provider
To configure the provider, you need to pass the `clientId` and `clientSecret` to `socialProviders.google` in your auth configuration.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
google: { // [!code highlight]
clientId: process.env.GOOGLE_CLIENT_ID as string, // [!code highlight]
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, // [!code highlight]
}, // [!code highlight]
},
})
```
## Usage
### Sign In with Google
To sign in with Google, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
* `provider`: The provider to use. It should be set to `google`.
```ts title="auth-client.ts" /
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient()
const signIn = async () => {
const data = await authClient.signIn.social({
provider: "google"
})
}
```
### Sign In with Google With ID Token
To sign in with Google using the ID Token, you can use the `signIn.social` function to pass the ID Token.
This is useful when you have the ID Token from Google on the client-side and want to use it to sign in on the server.
If id token is provided no redirection will happen, and the user will be signed in directly.
```ts title="auth-client.ts"
const data = await authClient.signIn.social({
provider: "google",
idToken: {
token: // Google ID Token,
accessToken: // Google Access Token
}
})
```
If you want to use google one tap, you can use the [One Tap Plugin](/docs/plugins/one-tap) guide.
file: ./content/docs/authentication/linkedin.mdx
meta: {
"title": "LinkedIn",
"description": "LinkedIn Provider"
}
### Get your LinkedIn credentials
To use LinkedIn sign in, you need a client ID and client secret. You can get them from the [LinkedIn Developer Portal](https://www.linkedin.com/developers/).
Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/linkedin` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly.
In the LinkedIn portal under products you need the **Sign In with LinkedIn using OpenID Connect** product.
There are some different Guides here:
[Authorization Code Flow (3-legged OAuth) (Outdated)](https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow)
[Sign In with LinkedIn using OpenID Connect](https://learn.microsoft.com/en-us/linkedin/consumer/integrations/self-serve/sign-in-with-linkedin-v2?context=linkedin%2Fconsumer%2Fcontext)
### Configure the provider
To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
linkedin: { // [!code highlight]
clientId: process.env.LINKEDIN_CLIENT_ID as string, // [!code highlight]
clientSecret: process.env.LINKEDIN_CLIENT_SECRET as string, // [!code highlight]
}, // [!code highlight]
},
})
```
### Sign In with LinkedIn
To sign in with LinkedIn, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
* `provider`: The provider to use. It should be set to `linkedin`.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient()
const signIn = async () => {
const data = await authClient.signIn.social({
provider: "linkedin"
})
}
```
file: ./content/docs/authentication/microsoft.mdx
meta: {
"title": "Microsoft",
"description": "Microsoft provider setup and usage."
}
Enabling OAuth with Microsoft Azure Entra ID (formerly Active Directory) allows your users to sign in and sign up to your application with their Microsoft account.
### Get your Microsoft credentials
To use Microsoft as a social provider, you need to get your Microsoft credentials. Which involves generating your own Client ID and Client Secret using your Microsoft Entra ID dashboard account.
Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/microsoft` for local development. For production, you should change it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly.
see the [Microsoft Entra ID documentation](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app) for more information.
### Configure the provider
To configure the provider, you need to pass the `clientId` and `clientSecret` to `socialProviders.microsoft` in your auth configuration.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
microsoft: { // [!code highlight]
clientId: process.env.MICROSOFT_CLIENT_ID as string, // [!code highlight]
clientSecret: process.env.MICROSOFT_CLIENT_SECRET as string, // [!code highlight]
// Optional
tenantId: 'common', // [!code highlight]
requireSelectAccount: true // [!code highlight]
}, // [!code highlight]
},
})
```
## Sign In with Microsoft
To sign in with Microsoft, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
* `provider`: The provider to use. It should be set to `microsoft`.
```ts title="auth-client.ts" /
import { createAuthClient } from "better-auth/client";
const authClient = createAuthClient();
const signIn = async () => {
const data = await authClient.signIn.social({
provider: "microsoft",
callbackURL: "/dashboard", //the url to redirect to after the sign in
});
};
```
file: ./content/docs/authentication/other-social-providers.mdx
meta: {
"title": "Other Social Providers",
"description": "Other social providers setup and usage."
}
Better Auth providers out of the box support for the [Generic Oauth Plugin](/docs/plugins/generic-oauth) which allows you to use any social provider that implements the OAuth2 protocol or OpenID Connect (OIDC) flows.
To use a provider that is not supported out of the box, you can use the [Generic Oauth Plugin](/docs/plugins/generic-oauth).
## Installation
### Add the plugin to your auth config
To use the Generic OAuth plugin, add it to your auth config.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { genericOAuth } from "better-auth/plugins" // [!code highlight]
export const auth = betterAuth({
// ... other config options
plugins: [
genericOAuth({ // [!code highlight]
config: [ // [!code highlight]
{ // [!code highlight]
providerId: "provider-id", // [!code highlight]
clientId: "test-client-id", // [!code highlight]
clientSecret: "test-client-secret", // [!code highlight]
discoveryUrl: "https://auth.example.com/.well-known/openid-configuration", // [!code highlight]
// ... other config options // [!code highlight]
}, // [!code highlight]
// Add more providers as needed // [!code highlight]
] // [!code highlight]
}) // [!code highlight]
]
})
```
### Add the client plugin
Include the Generic OAuth client plugin in your authentication client instance.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
import { genericOAuthClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [
genericOAuthClient()
]
})
```
Read more about installation and usage of the Generic Oauth plugin
[here](/docs/plugins/generic-oauth#usage).
## Example usage
### Slack Example
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { genericOAuth } from "better-auth/plugins";
export const auth = betterAuth({
// ... other config options
plugins: [
genericOAuth({
config: [
{
providerId: "slack",
clientId: process.env.SLACK_CLIENT_ID as string,
clientSecret: process.env.SLACK_CLIENT_SECRET as string,
authorizationUrl: "https://slack.com/oauth/v2/authorize",
tokenUrl: "https://slack.com/api/oauth.v2.access",
scopes: ["users:read", "users:read.email"], // and more...
},
],
}),
],
});
```
```ts title="sign-in.ts"
const response = await authClient.signIn.oauth2({
providerId: "slack",
callbackURL: "/dashboard", // the path to redirect to after the user is authenticated
});
```
### Instagram Example
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { genericOAuth } from "better-auth/plugins";
export const auth = betterAuth({
// ... other config options
plugins: [
genericOAuth({
config: [
{
providerId: "instagram",
clientId: process.env.INSTAGRAM_CLIENT_ID as string,
clientSecret: process.env.INSTAGRAM_CLIENT_SECRET as string,
authorizationUrl: "https://api.instagram.com/oauth/authorize",
tokenUrl: "https://api.instagram.com/oauth/access_token",
scopes: ["user_profile", "user_media"],
},
],
}),
],
});
```
```ts title="sign-in.ts"
const response = await authClient.signIn.oauth2({
providerId: "instagram",
callbackURL: "/dashboard", // the path to redirect to after the user is authenticated
});
```
### Coinbase Example
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { genericOAuth } from "better-auth/plugins";
export const auth = betterAuth({
// ... other config options
plugins: [
genericOAuth({
config: [
{
providerId: "coinbase",
clientId: process.env.COINBASE_CLIENT_ID as string,
clientSecret: process.env.COINBASE_CLIENT_SECRET as string,
authorizationUrl: "https://www.coinbase.com/oauth/authorize",
tokenUrl: "https://api.coinbase.com/oauth/token",
scopes: ["wallet:user:read"], // and more...
},
],
}),
],
});
```
```ts title="sign-in.ts"
const response = await authClient.signIn.oauth2({
providerId: "coinbase",
callbackURL: "/dashboard", // the path to redirect to after the user is authenticated
});
```
file: ./content/docs/authentication/reddit.mdx
meta: {
"title": "Reddit",
"description": "Reddit provider setup and usage."
}
### Get your Reddit Credentials
To use Reddit sign in, you need a client ID and client secret. You can get them from the [Reddit Developer Portal](https://www.reddit.com/prefs/apps).
1. Click "Create App" or "Create Another App"
2. Select "web app" as the application type
3. Set the redirect URL to `http://localhost:3000/api/auth/callback/reddit` for local development
4. For production, set it to your application's domain (e.g. `https://example.com/api/auth/callback/reddit`)
5. After creating the app, you'll get the client ID (under the app name) and client secret
If you change the base path of the auth routes, make sure to update the redirect URL accordingly.
### Configure the provider
To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
reddit: {
clientId: process.env.REDDIT_CLIENT_ID as string,
clientSecret: process.env.REDDIT_CLIENT_SECRET as string,
},
},
})
```
### Sign In with Reddit
To sign in with Reddit, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
* `provider`: The provider to use. It should be set to `reddit`.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient()
const signIn = async () => {
const data = await authClient.signIn.social({
provider: "reddit"
})
}
```
## Additional Configuration
### Scopes
By default, Reddit provides basic user information. If you need additional permissions, you can specify scopes in your auth configuration:
```ts title="auth.ts"
export const auth = betterAuth({
socialProviders: {
reddit: {
clientId: process.env.REDDIT_CLIENT_ID as string,
clientSecret: process.env.REDDIT_CLIENT_SECRET as string,
duration: "permanent",
scopes: ["identity", "read", "submit"] // Add required scopes
},
},
})
```
Common Reddit scopes include:
* `identity`: Access basic account information
* `read`: Access posts and comments
* `submit`: Submit posts and comments
* `subscribe`: Manage subreddit subscriptions
* `history`: Access voting history
For a complete list of available scopes, refer to the [Reddit OAuth2 documentation](https://www.reddit.com/dev/api/oauth).
file: ./content/docs/authentication/spotify.mdx
meta: {
"title": "Spotify",
"description": "Spotify provider setup and usage."
}
### Get your Spotify Credentials
To use Spotify sign in, you need a client ID and client secret. You can get them from the [Spotify Developer Portal](https://developer.spotify.com/dashboard/applications).
Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/spotify` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly.
### Configure the provider
To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
spotify: { // [!code highlight]
clientId: process.env.SPOTIFY_CLIENT_ID as string, // [!code highlight]
clientSecret: process.env.SPOTIFY_CLIENT_SECRET as string, // [!code highlight]
}, // [!code highlight]
},
})
```
### Sign In with Spotify
To sign in with Spotify, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
* `provider`: The provider to use. It should be set to `spotify`.
```ts title="auth-client.ts" /
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient()
const signIn = async () => {
const data = await authClient.signIn.social({
provider: "spotify"
})
}
```
file: ./content/docs/authentication/twitch.mdx
meta: {
"title": "Twitch",
"description": "Twitch provider setup and usage."
}
### Get your Twitch Credentials
To use Twitch sign in, you need a client ID and client secret. You can get them from the [Twitch Developer Portal](https://dev.twitch.tv/console/apps).
Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/twitch` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly.
### Configure the provider
To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
twitch: { // [!code highlight]
clientId: process.env.TWITCH_CLIENT_ID as string, // [!code highlight]
clientSecret: process.env.TWITCH_CLIENT_SECRET as string, // [!code highlight]
}, // [!code highlight]
}
})
```
### Sign In with Twitch
To sign in with Twitch, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
* `provider`: The provider to use. It should be set to `twitch`.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient()
const signIn = async () => {
const data = await authClient.signIn.social({
provider: "twitch"
})
}
```
file: ./content/docs/authentication/twitter.mdx
meta: {
"title": "Twitter (X)",
"description": "Twitter provider setup and usage."
}
### Get your Twitter Credentials
Get your Twitter credentials from the [Twitter Developer Portal](https://developer.twitter.com/en/portal/dashboard).
Make sure to set the redirect URL to `http://localhost:3000/api/auth/callback/twitter` for local development. For production, you should set it to the URL of your application. If you change the base path of the auth routes, you should update the redirect URL accordingly.
{ /*
If twitter doesn't return the email address, the authentication won't be successful. Make sure to ask for the email address when you create the Twitter app.
*/}
Twitter API v2 does not provide email addresses. As a workaround, the user's `email` field uses the `username` value instead.
### Configure the provider
To configure the provider, you need to import the provider and pass it to the `socialProviders` option of the auth instance.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
socialProviders: {
twitter: { // [!code highlight]
clientId: process.env.TWITTER_CLIENT_ID, // [!code highlight]
clientSecret: process.env.TWITTER_CLIENT_SECRET, // [!code highlight]
}, // [!code highlight]
},
})
```
### Sign In with Twitter
To sign in with Twitter, you can use the `signIn.social` function provided by the client. The `signIn` function takes an object with the following properties:
* `provider`: The provider to use. It should be set to `twitter`.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient()
const signIn = async () => {
const data = await authClient.signIn.social({
provider: "twitter"
})
}
```
file: ./content/docs/concepts/api.mdx
meta: {
"title": "API",
"description": "Better Auth API."
}
When you create a new Better Auth instance, it gives you an `api` object. This object provides functions to interact with the server while your code is running server-side. You can use these functions to call any API endpoint on the server.
Any endpoint added to Better Auth, whether from plugins or the core, will be accessible through the `api` object.
## Calling API Endpoints on the Server
To call an API endpoint on the server, import your `auth` instance and call the endpoint using the `api` object.
```ts title="server.ts"
import { betterAuth } from "better-auth";
import { headers } from "next/headers";
export const auth = betterAuth({
//...
})
// calling get session on the server
await auth.api.getSession({
headers: headers()
})
```
Unlike the client, the server needs the values to be passed as an object with the key `body` for the body, `headers` for the headers, and `query` for the query.
```ts title="server.ts"
await auth.api.signInEmail({
body: {
email: "",
password: ""
}
})
```
Better auth API endpoints are built on top of [better-call](https://github.com/bekacru/better-call), a tiny web framework that lets you call REST API endpoints as if they were regular functions and allows us to easily infer client types from the server.
### Getting the `Response` Object
When you invoke an API endpoint on the server, it will return a standard JavaScript object or array directly. To get the `Response` object instead, you can use the `asResponse` option.
```ts title="server.ts"
const response = await auth.api.signInEmail({
body: {
email: "",
password: ""
},
asResponse: true
})
```
### Error Handling
When you call an API endpoint in the server, it will throw an error if the request fails. You can catch the error and handle it as you see fit. The error instance is an instance of `APIError`.
```ts title="server.ts"
import { APIError } from "better-auth/api";
try {
await auth.api.signInEmail({
body: {
email: "",
password: ""
}
})
} catch (error) {
if (error instanceof APIError) {
console.log(error.message, error.status)
}
}
```
file: ./content/docs/concepts/cli.mdx
meta: {
"title": "CLI",
"description": "Built in CLI for managing your project."
}
Better Auth comes with a built-in CLI to help you manage the database schema needed for both core functionality and plugins.
## Generate
The `generate` command creates the schema required by Better Auth. If you're using a database adapter like Prisma or Drizzle, this command will generate the right schema for your ORM. If you're using the built-in Kysely adapter, it will generate an SQL file you can run directly on your database.
```bash title="Terminal"
npx @better-auth/cli@latest generate
```
### Options
* `--output` - Where to save the generated schema. For Prisma, it will be saved in prisma/schema.prisma. For Drizzle, it goes to schema.ts in your project root. For Kysely, it’s an SQL file saved as schema.sql in your project root.
* `--config` - The path to your Better Auth config file. By default, the CLI will search for a better-auth.ts file in **./**, **./utils**, **./lib**, or any of these directories under `src` directory.
* `--y` - Skip the confirmation prompt and generate the schema directly.
## Migrate
The migrate command applies the Better Auth schema directly to your database. This is available if you’re using the built-in Kysely adapter.
```bash title="Terminal"
npx @better-auth/cli@latest migrate
```
### Options
* `--config` - The path to your Better Auth config file. By default, the CLI will search for a better-auth.ts file in **./**, **./utils**, **./lib**, or any of these directories under `src` directory.
* `--y` - Skip the confirmation prompt and apply the schema directly.
## Common Issues
**Error: Cannot find module X**
If you see this error, it means the CLI can’t resolve imported modules in your Better Auth config file. We're working on a fix for many of these issues, but in the meantime, you can try the following:
* Remove any import aliases in your config file and use relative paths instead. After running the CLI, you can revert to using aliases.
## Secret
The CLI also provides a way to generate a secret key for your Better Auth instance.
```bash title="Terminal"
npx @better-auth/cli@latest secret
```
file: ./content/docs/concepts/client.mdx
meta: {
"title": "Client",
"description": "Better Auth client library for authentication."
}
Better Auth offers a client library compatible with popular frontend frameworks like React, Vue, Svelte, and more. This client library includes a set of functions for interacting with the Better Auth server. Each framework's client library is built on top of a core client library that is framework-agnostic, so that all methods and hooks are consistently available across all client libraries.
## Installation
If you haven't already, install better-auth.
```bash
npm i better-auth
```
```bash
pnpm add better-auth
```
```bash
yarn add better-auth
```
```bash
bun add better-auth
```
## Create Client Instance
Import `createAuthClient` from the package for your framework (e.g., "better-auth/react" for React). Call the function to create your client. Pass the base URL of your auth server. If the auth server is running on the same domain as your client, you can skip this step.
If you're using a different base path other than `/api/auth`, make sure to pass the whole URL, including the path. (e.g., `http://localhost:3000/custom-path/auth`)
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/client"
export const authClient = createAuthClient({
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
})
```
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/react"
export const authClient = createAuthClient({
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
})
```
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/vue"
export const authClient = createAuthClient({
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
})
```
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/svelte"
export const authClient = createAuthClient({
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
})
```
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/solid"
export const authClient = createAuthClient({
baseURL: "http://localhost:3000" // the base url of your auth server // [!code highlight]
})
```
## Usage
Once you've created your client instance, you can use the client to interact with the Better Auth server. The client provides a set of functions by default and they can be extended with plugins.
**Example: Sign In**
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient()
await authClient.signIn.email({
email: "test@user.com",
password: "password1234"
})
```
### Hooks
On top of normal methods, the client provides hooks to easily access different reactive data. Every hook is available in the root object of the client and they all start with `use`.
**Example: useSession**
```tsx title="user.tsx"
//make sure you're using the react client
import { createAuthClient } from "better-auth/react"
const { useSession } = createAuthClient() // [!code highlight]
export function User() {
const {
data: session,
isPending, //loading state
error, //error object
refetch //refetch the session
} = useSession()
return (
//...
)
}
```
```vue title="user.vue"
{{ session.data }}
```
```svelte title="user.svelte"
{#if $session}
{$session?.data?.user.name}
{$session?.data?.user.email}
{:else}
{/if}
```
```tsx title="user.tsx"
import { client } from "~/lib/client";
import { Show } from 'solid-js';
export default function Home() {
const session = client.useSession()
return (
Log in}
>
);
}
```
### Fetch Options
The client uses a library called [better fetch](https://better-fetch.vercel.app) to make requests to the server.
Better fetch is a wrapper around the native fetch API that provides a more convenient way to make requests. It's created by the same team behind Better Auth and is designed to work seamlessly with it.
You can pass any default fetch options to the client by passing `fetchOptions` object to the `createAuthClient`.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient({
fetchOptions: {
//any better-fetch options
},
})
```
You can also pass fetch options to most of the client functions. Either as the second argument or as a property in the object.
```ts title="auth-client.ts"
await authClient.signIn.email({
email: "email@email.com",
password: "password1234",
}, {
onSuccess(ctx) {
//
}
})
//or
await authClient.signIn.email({
email: "email@email.com",
password: "password1234",
fetchOptions: {
onSuccess(ctx) {
//
}
},
})
```
### Handling Errors
Most of the client functions return a response object with the following properties:
* `data`: The response data.
* `error`: The error object if there was an error.
the error object contains the following properties:
* `message`: The error message. (e.g., "Invalid email or password")
* `status`: The HTTP status code.
* `statusText`: The HTTP status text.
```ts title="auth-client.ts"
const { data, error } = await authClient.signIn.email({
email: "email@email.com",
password: "password1234"
})
if (error) {
//handle error
}
```
If the actions accepts a `fetchOptions` option, you can pass `onError` callback to handle errors.
```ts title="auth-client.ts"
await authClient.signIn.email({
email: "email@email.com",
password: "password1234",
}, {
onError(ctx) {
//handle error
}
})
//or
await authClient.signIn.email({
email: "email@email.com",
password: "password1234",
fetchOptions: {
onError(ctx) {
//handle error
}
}
})
```
Hooks like `useSession` also return an error object if there was an error fetching the session. On top of that, they also return a `isPending` property to indicate if the request is still pending.
```ts title="auth-client.ts"
const { data, error, isPending } = useSession()
if (error) {
//handle error
}
```
#### Error Codes
The client instance contains $ERROR\_CODES object that contains all the error codes returned by the server. You can use this to handle error translations or custom error messages.
```ts title="auth-client.ts"
const authClient = createAuthClient();
type ErrorTypes = Partial<
Record<
keyof typeof client.$ERROR_CODES,
{
en: string;
es: string;
}
>
>;
const errorCodes = {
USER_ALREADY_EXISTS: {
en: "user already registered",
es: "usuario ya registrada",
},
} satisfies ErrorTypes;
const getErrorMessage = (code: string, lang: "en" | "es") => {
if (code in errorCodes) {
return errorCodes[code as keyof typeof errorCodes][lang];
}
return "";
};
const { error } = await authClient.signUp.email({
email: "user@email.com",
password: "password",
name: "User",
});
if(error?.code){
alert(getErrorMessage(error.code), "en");
}
```
### Plugins
You can extend the client with plugins to add more functionality. Plugins can add new functions to the client or modify existing ones.
**Example: Magic Link Plugin**
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
import { magicLinkClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [
magicLinkClient()
]
})
```
once you've added the plugin, you can use the new functions provided by the plugin.
```ts title="auth-client.ts"
await authClient.signIn.magicLink({
email: "test@email.com"
})
```
file: ./content/docs/concepts/cookies.mdx
meta: {
"title": "Cookies",
"description": "Learn how cookies are used in Better Auth."
}
Cookies are used to store data such as session tokens, OAuth state, and more. All cookies are signed using the `secret` key provided in the auth options.
### Cookie Prefix
Better Auth cookies will follow `${prefix}.${cookie_name}` format by default. The prefix will be "better-auth" by default. You can change the prefix by setting `cookiePrefix` in the `advanced` object of the auth options.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
advanced: {
cookiePrefix: "my-app"
}
})
```
### Custom Cookies
All cookies are `httpOnly` and `secure` if the server is running in production mode.
If you want to set custom cookie names and attributes, you can do so by setting `cookieOptions` in the `advanced` object of the auth options.
By default, Better Auth uses the following cookies:
* `session_token` to store the session token
* `session_data` to store the session data if cookie cache is enabled
* `dont_remember` to store the `dont_remember` flag if remember me is disabled
Plugins may also use cookies to store data. For example, the Two Factor Authentication plugin uses the `two_factor` cookie to store the two-factor authentication state.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
advanced: {
cookies: {
session_token: {
name: "custom_session_token",
attributes: {
// Set custom cookie attributes
}
},
}
}
})
```
### Cross Subdomain Cookies
Sometimes you may need to share cookies across subdomains. For example, if you have `app.example.com` and `example.com`, and if you authenticate on `example.com`, you may want to access the same session on `app.example.com`.
By default, cookies are not shared between subdomains. However, if you need to access the same session across different subdomains, you can enable cross-subdomain cookies by configuring `crossSubDomainCookies` and `defaultCookieAttributes` in the `advanced` object of the auth options.
The leading period of the `domain` attribute broadens the cookie's scope beyond your main domain, making it accessible across all subdomains.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
advanced: {
crossSubDomainCookies: {
enabled: true,
domain: ".example.com", // Domain with a leading period
},
defaultCookieAttributes: {
secure: true,
httpOnly: true,
sameSite: "none", // Allows CORS-based cookie sharing across subdomains
},
},
trustedOrigins: [
'https://example.com',
'https://app1.example.com',
'https://app2.example.com',
],
})
```
Setting your `sameSite` cookie attribute to `none` makes you vulnerable to CSRF attacks. To mitigate risks, configure `trustedOrigins` with an array of authorized origins to allow.
### Secure Cookies
By default, cookies are secure only when the server is running in production mode. You can force cookies to be always secure by setting `useSecureCookies` to `true` in the `advanced` object in the auth options.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
advanced: {
useSecureCookies: true
}
})
```
file: ./content/docs/concepts/database.mdx
meta: {
"title": "Database",
"description": "Learn how to use a database with Better Auth."
}
Better Auth requires a database connection to store data. It comes with a query builder called Kysely to manage and query your database. The database will be used to store data such as users, sessions, and more. Plugins can also define their own database tables to store data.
You can pass a database connection to Better Auth by passing a supported database instance, a dialect instance or a Kysely instance in the database options.
**Example: Sqlite**
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import Database from "better-sqlite3"
export const auth = betterAuth({
database: new Database("database.sqlite")
})
```
**Example: Postgres**
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { Pool } from "pg"
export const auth = betterAuth({
database: new Pool({
connectionString: "postgres://user:password@localhost:5432/database"
})
})
```
**Example: MySQL**
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { createPool } from "mysql2/promise"
export const auth = betterAuth({
database: createPool({
host: "localhost",
user: "root",
password: "password",
database: "database"
})
})
```
**Example: Custom Dialect using libSQL**
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { LibsqlDialect } from "@libsql/kysely-libsql";
export const auth = betterAuth({
database: {
dialect: new LibsqlDialect({
url: process.env.TURSO_DATABASE_URL || "",
authToken: process.env.TURSO_AUTH_TOKEN || "",
}),
type: "sqlite"
},
})
```
See Kysely Dialects for more dialects supported by Kysely.
**Example: Custom Kysely Instance**
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { db } from "./db"
export const auth = betterAuth({
database: {
db: db,
type: "sqlite" // or "mysql", "postgres" or "mssql"
}
})
```
## Using Adapters
If your database is managed by an ORM like Prisma or Drizzle, you can use the corresponding adapter to connect to the database. Better Auth comes with built-in adapters for Prisma and Drizzle. You can pass the adapter to the `database` object in the auth options.
### Prisma Adapter
The Prisma adapter expects a prisma client instance and a provider key that specifies the database provider to use. The provider key can be `sqlite`, `postgres`, `mysql`, or any other supported by prisma.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: "sqlite"
})
})
```
### Drizzle adapter
The Drizzle adapter expects a drizzle client instance and a provider key that specifies the database provider to use. The provider key can be `sqlite`, `pg` or `mysql`.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { db } from "./drizzle";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "sqlite", // or "pg" or "mysql"
})
})
```
#### Mapping Schema
The Drizzle adapter expects the schema you define to match the table names. For example, if your Drizzle schema maps the `user` table to `users`, you need to manually pass the schema and map it to the user table.
```ts
import { betterAuth } from "better-auth";
import { db } from "./drizzle";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { schema } from "./schema";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "sqlite", // or "pg" or "mysql"
schema: {
...schema,
user: schema.users,
},
//if all of them are just using plural form, you can just pass the option below
usePlural: true
})
})
```
### MongoDB Adapter
The MongoDB adapter expects a mongodb client instance and a database name. The adapter will create a new database with the provided name if it doesn't exist.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { MongoClient } from "mongodb";
import { mongodbAdapter } from "better-auth/adapters/mongodb";
const client = new MongoClient("mongodb://localhost:27017/database");
const db = client.db()
export const auth = betterAuth({
database: mongodbAdapter(db)
})
```
## CLI
Better Auth comes with a CLI tool to manage database migrations and generate schema.
### Running Migrations
The cli checks your database and prompts you to add missing tables or update existing ones with new columns. This is only supported for the built-in Kysely adapter. For other adapters, you can use the `generate` command to create the schema and handle the migration through your ORM.
```bash
npx @better-auth/cli migrate
```
### Generating Schema
Better Auth also provides a `generate` command to generate the schema required by Better Auth. The `generate` command creates the schema required by Better Auth. If you're using a database adapter like Prisma or Drizzle, this command will generate the right schema for your ORM. If you're using the built-in Kysely adapter, it will generate an SQL file you can run directly on your database.
```bash
npx @better-auth/cli generate
```
See the [CLI](/docs/concepts/cli) documentation for more information on the CLI.
If you prefer adding tables manually, you can do that as well. The core schema required by Better Auth is described below and you can find additional schema required by plugins in the plugin documentation.
## Secondary Storage
Secondary storage in Better Auth allows you to use key-value stores for managing session data, rate limiting counters, etc. This can be useful when you want to offload the storage of this intensive records to a high performance storage or even RAM.
### Implementation
To use secondary storage, implement the `SecondaryStorage` interface:
```typescript
interface SecondaryStorage {
get: (key: string) => Promise
set: (
key: string,
value: string,
ttl?: number,
) => Promise;
delete: (key: string) => Promise;
}
```
Then, provide your implementation to the `betterAuth` function:
```typescript
betterAuth({
// ... other options
secondaryStorage: {
// Your implementation here
}
})
```
**Example: Redis Implementation**
Here's a basic example using Redis:
```typescript
import { createClient } from "redis";
import { betterAuth } from "better-auth";
const redis = createClient();
await redis.connect();
export const auth = betterAuth({
// ... other options
secondaryStorage: {
get: async (key) => {
const value = await redis.get(key);
return value ? JSON.stringify(value) : null;
},
set: async (key, value, ttl) => {
if (ttl) await redis.set(key, value, { EX: ttl });
// or for ioredis:
// if (ttl) await redis.set(key, value, 'EX', ttl)
else await redis.set(key, value);
},
delete: async (key) => {
await redis.del(key);
}
}
});
```
This implementation allows Better Auth to use Redis for storing session data and rate limiting counters. You can also add prefixes to the keys names.
## Core Schema
Better Auth requires the following tables to be present in the database. The types are in `typescript` format. You can use corresponding types in your database.
### User
Table Name: `user`
### Session
Table Name: `session`
### Account
Table Name: `account`
### Verification
Table Name: `verification`
## Custom Tables
Better Auth allows you to customize the table names and column names for the core schema. You can also extend the core schema by adding additional fields to the user and session tables.
### Custom Table Names
You can customize the table names and column names for the core schema by using the `modelName` and `fields` properties in your auth config:
```ts title="auth.ts"
export const auth = betterAuth({
user: {
modelName: "users",
fields: {
name: "full_name",
email: "email_address"
}
},
session: {
modelName: "user_sessions",
fields: {
userId: "user_id"
}
}
})
```
Type inference in your code will still use the original field names (e.g., `user.name`, not `user.full_name`).
To customize table names and column name for plugins, you can use the `schema` property in the plugin config:
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
twoFactor({
schema: {
user: {
fields: {
twoFactorEnabled: "two_factor_enabled",
secret: "two_factor_secret",
},
},
},
}),
],
});
```
### Extending Core Schema
Better Auth provides a type-safe way to extend the `user` and `session` schemas. You can add custom fields to your auth config, and the CLI will automatically update the database schema. These additional fields will be properly inferred in functions like `useSession`, `signUp.email`, and other endpoints that work with user or session objects.
To add custom fields, use the `additionalFields` property in the `user` or `session` object of your auth config. The `additionalFields` object uses field names as keys, with each value being a `FieldAttributes` object containing:
* `type`: The data type of the field (e.g., "string", "number", "boolean").
* `required`: A boolean indicating if the field is mandatory.
* `defaultValue`: The default value for the field (note: this only applies in the JavaScript layer; in the database, the field will be optional).
* `input`: This determines whether a value can be provided when creating a new record (default: `true`). If there are additional fields, like `role`, that should not be provided by the user during signup, you can set this to `false`.
Here's an example of how to extend the user schema with additional fields:
```ts title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
user: {
additionalFields: {
role: {
type: "string",
required: false,
defaultValue: "user",
input: false // don't allow user to set role
},
lang: {
type: "string",
required: false,
defaultValue: "en",
}
}
}
})
```
Now you can access the additional fields in your application logic.
```ts
//on signup
const res = await auth.api.signUpEmail({
email: "test@example.com",
password: "password",
name: "John Doe",
lang: "fr"
})
//user object
res.user.role // > "admin"
res.user.lang // > "fr"
```
See the [Typescript](/docs/concepts/typescript#inferring-additional-fields-on-client) documentation for more information on how to infer additional fields on the client side.
If you're using social / OAuth providers, you may want to provide `mapProfileToUser` to map the profile data to the user object. So, you can populate additional fields from the provider's profile.
**Example: Mapping Profile to User For `firstName` and `lastName`**
```ts title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
socialProviders: {
github: {
clientId: "YOUR_GITHUB_CLIENT_ID",
clientSecret: "YOUR_GITHUB_CLIENT_SECRET",
mapProfileToUser: (profile) => {
return {
firstName: profile.name.split(" ")[0],
lastName: profile.name.split(" ")[1],
}
}
},
google: {
clientId: "YOUR_GOOGLE_CLIENT_ID",
clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
mapProfileToUser: (profile) => {
return {
firstName: profile.given_name,
lastName: profile.family_name,
}
}
}
}
})
```
### ID Generation
Better Auth by default will generate unique IDs for users, sessions, and other entities. If you want to customize how IDs are generated, you can configure this in the `advanced` object in your auth config.
You can also disable ID generation by setting the `generateId` option to `false`. This will assume your database will generate the ID automatically.
**Example: Automatic Database IDs**
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { db } from "./db";
export const auth = betterAuth({
database: {
db: db
},
advanced: {
generateId: false,
},
})
```
### Database Hooks
Database hooks allow you to define custom logic that can be executed during the lifecycle of core database operations in Better Auth. You can create hooks for the following models: **user**, **session**, and **account**.
There are two types of hooks you can define:
#### 1. Before Hook
* **Purpose**: This hook is called before the respective entity (user, session, or account) is created or updated.
* **Behavior**: If the hook returns `false`, the operation will be aborted. And If it returns a data object, it'll replace the orginal payload.
#### 2. After Hook
* **Purpose**: This hook is called after the respective entity is created or updated.
* **Behavior**: You can perform additional actions or modifications after the entity has been successfully created or updated.
**Example Usage**
```typescript title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
databaseHooks: {
user: {
create: {
before: async (user) => {
// Modify the user object before it is created
return {
data: {
...user,
firstName: user.name.split(" ")[0],
lastName: user.name.split(" ")[1]
}
}
},
after: async (user) => {
//perform additional actions, like creating a stripe customer
},
},
},
}
})
```
#### Throwing Errors
If you want to stop the database hook from proceeding, you can throw errors using the `APIError` class imported from `better-auth/api`.
```typescript title="auth.ts"
import { betterAuth } from "better-auth";
import { APIError } from "better-auth/api";
export const auth = betterAuth({
databaseHooks: {
user: {
create: {
before: async (user) => {
if (user.isAgreedToTerms === false) { // Your special condition.
// Send the API error.
throw new APIError("BAD_REQUEST", {
message: "User must agree to the TOS before signing up.",
});
}
return {
data: user
};
},
},
},
}
})
```
## Plugins Schema
Plugins can define their own tables in the database to store additional data. They can also add columns to the core tables to store additional data. For example, the two factor authentication plugin adds the following columns to the `user` table:
* `twoFactorEnabled`: Whether two factor authentication is enabled for the user.
* `twoFactorSecret`: The secret key used to generate TOTP codes.
* `twoFactorBackupCodes`: Encrypted backup codes for account recovery.
To add new tables and columns to your database, you have two options:
`CLI`: Use the migrate or generate command. These commands will scan your database and guide you through adding any missing tables or columns.
`Manual Method`: Follow the instructions in the plugin documentation to manually add tables and columns.
Both methods ensure your database schema stays up-to-date with your plugins' requirements.
file: ./content/docs/concepts/email.mdx
meta: {
"title": "Email",
"description": "Learn how to use email with Better Auth."
}
Email is a key part of Better Auth, required for all users regardless of their authentication method. Better Auth provides email and password authentication out of the box, and a lot of utilities to help you manage email verification, password reset, and more.
## Email Verification
Email verification is a security feature that ensures users provide a valid email address. It helps prevent spam and abuse by confirming that the email address belongs to the user.
### Adding Email Verification to Your App
To enable email verification, you need to pass a function that sends a verification email with a link.
* **sendVerificationEmail**: This function is triggered when email verification starts. It accepts a data object with the following properties:
* `user`: The user object containing the email address.
* `url`: The verification URL the user must click to verify their email.
* `token`: The verification token used to complete the email verification to be used when implementing a custom verification URL.
and a `request` object as the second parameter.
```ts title="auth.ts"
import { betterAuth } from 'better-auth';
import { sendEmail } from './email'; // your email sending function
export const auth = betterAuth({
emailVerification: {
sendVerificationEmail: async ({ user, url, token }, request) => {
await sendEmail({
to: user.email,
subject: 'Verify your email address',
text: `Click the link to verify your email: ${url}`
})
}
}
})
```
### Triggering Email Verification
You can initiate email verification in two ways:
#### 1. During Sign-up
To automatically send a verification email at signup, set `emailVerification.sendOnSignUp` to `true`.
```ts title="auth.ts"
import { betterAuth } from 'better-auth';
export const auth = betterAuth({
emailVerification: {
sendOnSignUp: true
}
})
```
This sends a verification email when a user signs up. For social logins, email verification status is read from the SSO.
With `sendOnSignUp` enabled, when the user logs in with an SSO that does not claim the email as verified, Better Auth will dispatch a verification email, but the verification is not required to login even when `requireEmailVerification` is enabled.
#### 2. Require Email Verification
If you enable require email verification, users must verify their email before they can log in. And every time a user tries to sign in, `sendVerificationEmail` is called.
This only works if you have `sendVerificationEmail` implemented and if the user is trying to sign in with email and password.
```ts title="auth.ts"
export const auth = betterAuth({
emailAndPassword: {
requireEmailVerification: true
}
})
```
if a user tries to sign in without verifying their email, you can handle the error and show a message to the user.
```ts title="auth-client.ts"
await authClient.signIn.email({
email: "email@example.com",
password: "password"
}, {
onError: (ctx) => {
// Handle the error
if(ctx.error.status === 403) {
alert("Please verify your email address")
}
//you can also show the original error message
alert(ctx.error.message)
}
})
```
#### 3. Manually
You can also manually trigger email verification by calling `sendVerificationEmail`.
```ts
await authClient.sendVerificationEmail({
email: "user@email.com",
callbackURL: "/" // The redirect URL after verification
})
```
### Verifying the Email
If the user clicks the provided verification URL, their email is automatically verified, and they are redirected to the `callbackURL`.
For manual verification, you can send the user a custom link with the `token` and call the `verifyEmail` function.
```ts
authClient.verifyEmail({
query: {
token: "" // Pass the token here
}
})
```
### Auto SignIn After Verification
To sign in the user automatically after they successfully verify their email, set the `autoSignInAfterVerification` option to `true`:
```ts
const auth = betterAuth({
//...your other options
emailVerification: {
autoSignInAfterVerification: true
}
})
```
## Password Reset Email
Password reset allows users to reset their password if they forget it. Better Auth provides a simple way to implement password reset functionality.
You can enable password reset by passing a function that sends a password reset email with a link.
```ts title="auth.ts"
import { betterAuth } from 'better-auth';
import { sendEmail } from './email'; // your email sending function
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
sendResetPassword: async ({ user, url, token }, request) => {
await sendEmail({
to: user.email,
subject: 'Reset your password',
text: `Click the link to reset your password: ${url}`
})
}
}
})
```
Check out the [Email and Password](/docs/authentication/email-password#forget-password) guide for more details on how to implement password reset in your app.
file: ./content/docs/concepts/hooks.mdx
meta: {
"title": "Hooks",
"description": "Better Auth Hooks let you customize BetterAuth's behavior"
}
Hooks in Better Auth let you "hook into" the lifecycle and execute custom logic. They provide a way to customize Better Auth's behavior without writing a full plugin.
We highly recommend using hooks if you need to make custom adjustments to an endpoint rather than making another endpoint outside of better auth.
## Before Hooks
**Before hooks** run *before* an endpoint is executed. Use them to modify requests, pre validate data, or return early.
### Example: Enforce Email Domain Restriction
This hook ensures that users can only sign up if their email ends with `@example.com`:
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { createAuthMiddleware, APIError } from "better-auth/api";
export const auth = betterAuth({
hooks: {
before: createAuthMiddleware(async (ctx) => {
if (ctx.path !== "/sign-up/email") {
return;
}
if (!ctx.body?.email.endsWith("@example.com")) {
throw new APIError("BAD_REQUEST", {
message: "Email must end with @example.com",
});
}
}),
},
});
```
### Example: Modify Request Context
To adjust the request context before proceeding:
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { createAuthMiddleware } from "better-auth/api";
export const auth = betterAuth({
hooks: {
before: createAuthMiddleware(async (ctx) => {
if (ctx.path === "/sign-up/email") {
return {
context: {
...ctx,
body: {
...ctx.body,
name: "John Doe",
},
}
};
}
}),
},
});
```
## After Hooks
**After hooks** run *after* an endpoint is executed. Use them to modify responses.
### Example: Send a notification to your channel when a new user is registered
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { createAuthMiddleware } from "better-auth/api";
import { sendMessage } from "@/lib/notification"
export const auth = betterAuth({
hooks: {
after: createAuthMiddleware(async (ctx) => {
if(ctx.path.startsWith("/sign-up")){
const newSession = ctx.context.newSession;
if(newSession){
sendMessage({
type: "user-register",
name: newSession.user.name,
})
}
}
}),
},
});
```
## Ctx
When you call `createAuthMiddleware` a `ctx` object is passed that provides a lot of useful properties. Including:
* **Path:** `ctx.path` to get the current endpoint path.
* **Body:** `ctx.body` for parsed request body (available for POST requests).
* **Headers:** `ctx.headers` to access request headers.
* **Request:** `ctx.request` to access the request object (may not exist in server-only endpoints).
* **Query Parameters:** `ctx.query` to access query parameters.
* **Context**: `ctx.context` auth related context, useful for accessing new session, auth cookies configuration, password hashing, config...
and more.
### Request Response
This utilities allows you to get request information and to send response from a hook.
#### JSON Responses
Use `ctx.json` to send JSON responses:
```ts
const hook = createAuthMiddleware(async (ctx) => {
return ctx.json({
message: "Hello World",
});
});
```
#### Redirects
Use `ctx.redirect` to redirect users:
```ts
import { createAuthMiddleware } from "better-auth/api";
const hook = createAuthMiddleware(async (ctx) => {
throw ctx.redirect("/sign-up/name");
});
```
#### Cookies
* Set cookies: `ctx.setCookies` or `ctx.setSignedCookie`.
* Get cookies: `ctx.getCookies` or `ctx.getSignedCookies`.
Example:
```ts
import { createAuthMiddleware } from "better-auth/api";
const hook = createAuthMiddleware(async (ctx) => {
ctx.setCookies("my-cookie", "value");
await ctx.setSignedCookie("my-signed-cookie", "value", ctx.context.secret, {
maxAge: 1000,
});
const cookie = ctx.getCookies("my-cookie");
const signedCookie = await ctx.getSignedCookies("my-signed-cookie");
});
```
#### Errors
Throw errors with `APIError` for a specific status code and message:
```ts
import { createAuthMiddleware, APIError } from "better-auth/api";
const hook = createAuthMiddleware(async (ctx) => {
throw new APIError("BAD_REQUEST", {
message: "Invalid request",
});
});
```
### Context
The `ctx` object contains another `context` object inside that's meant to hold contexts related to auth. Including a newly created session on after hook, cookies configuration, password hasher and so on.
#### New Session
The newly created session after an endpoint is run. This only exist in after hook.
```ts title="auth.ts"
createAuthMiddleware(async (ctx) => {
const newSession = ctx.context.newSession
});
```
#### Predefined Auth Cookies
Access BetterAuth’s predefined cookie properties:
```ts title="auth.ts"
createAuthMiddleware(async (ctx) => {
const cookieName = ctx.context.authCookies.sessionToken.name;
});
```
#### Secret
You can access the `secret` for your auth instance on `ctx.context.secret`
#### Password
The password object provider `hash` and `verify`
* `ctx.context.password.hash`: let's you hash a given password.
* `ctx.context.password.verify`: let's you verify given `password` and a `hash`.
#### Adapter
Adapter exposes the adapter methods used by better auth. Including `findOne`, `findMany`, `create`, `delete`, `update` and `updateMany`. You generally should use your actually `db` instance from your orm rather than this adapter.
#### Internal Adapter
These are calls to your db that perform specific actions. `createUser`, `createSession`, `updateSession`...
This may be useful to use instead of using your db directly to get access to `databaseHooks`, proper `secondaryStorage` support and so on. If you're make a query similar to what exist in this internal adapter actions it's worth a look.
#### generateId
You can use `ctx.context.generateId` to generate Id for various reasons.
## Reusable Hooks
If you need to reuse a hook across multiple endpoints, consider creating a plugin. Learn more in the [Plugins Documentation](/docs/concepts/plugins).
file: ./content/docs/concepts/oauth.mdx
meta: {
"title": "OAuth",
"description": "How Better Auth handles OAuth"
}
Better Auth comes with built-in support for OAuth 2.0 and OpenID Connect. This allows you to authenticate users via popular OAuth providers like Google, Facebook, GitHub, and more.
If your desired provider isn’t directly supported, you can use the [Generic OAuth Plugin](/docs/plugins/generic-oauth) for custom integrations.
## Configuring Social Providers
To enable a social provider, you need to provide `clientId` and `clientSecret` for the provider.
Here’s an example of how to configure Google as a provider:
```ts title="auth.ts"
import { betterAuth } from 'better-auth';
export const auth = betterAuth({
// Other configurations...
socialProviders: {
google: {
clientId: 'YOUR_GOOGLE_CLIENT_ID',
clientSecret: 'YOUR_GOOGLE_CLIENT_SECRET',
}
}
})
```
### Other Provider Configurations
**scope** The scope of the access request. For example, `email` or `profile`.
**redirectURI** Custom redirect URI for the provider. By default, it uses `/api/auth/callback/${providerName}`.
**disableIdTokenSignIn:** Disables the use of the ID token for sign-in. By default, it’s enabled for some providers like Google and Apple.
**verifyIdToken** A custom function to verify the ID token.
**getUserInfo** A custom function to fetch user information from the provider. Given the tokens returned from the provider, this function should return the user’s information.
**mapProfileToUser** A custom function to map the user profile returned from the provider to the user object in your database.
Useful, if you have additional fields in your user object you want to populate from the provider’s profile. Or if you want to change how by default the user object is mapped.
```ts title="auth.ts"
import { betterAuth } from 'better-auth';
export const auth = betterAuth({
// Other configurations...
socialProviders: {
google: {
clientId: 'YOUR_GOOGLE_CLIENT_ID',
clientSecret: 'YOUR_GOOGLE_CLIENT_SECRET',
mapProfileToUser: (profile) => {
return {
firstName: profile.given_name,
lastName: profile.family_name,
}
}
}
}
})
```
## How OAuth Works in Better Auth
Here’s what happens when a user selects a provider to authenticate with:
1. **Configuration Check:** Ensure the necessary provider details (e.g., client ID, secret) are configured.
2. **State Generation:** Generate and save a state token in your database for CSRF protection.
3. **PKCE Support:** If applicable, create a PKCE code challenge and verifier for secure exchanges.
4. **Authorization URL Construction:** Build the provider’s authorization URL with parameters like client ID, redirect URI, state, etc. The callback URL usually follows the pattern `/api/auth/callback/${providerName}`.
5. **User Redirection:**
* If redirection is enabled, users are redirected to the provider’s login page.
* If redirection is disabled, the authorization URL is returned for the client to handle the redirection.
### Post-Login Flow
After the user completes the login process, the provider redirects them back to the callback URL with a code and state. Better Auth handles the rest:
1. **Token Exchange:** The code is exchanged for an access token and user information.
2. **User Handling:**
* If the user doesn’t exist, a new account is created.
* If the user exists, they are logged in.
* If the user has multiple accounts across providers, Better Auth links them based on your configuration. Learn more about [account linking](/docs/concepts/users-accounts#account-linking).
3. **Session Creation:** A new session is created for the user.
4. **Redirect:** Users are redirected to the specified URL provided during the initial request or `/`.
If any error occurs during the process, Better Auth handles it and redirects the user to the error URL (if provided) or the callbackURL. And it includes the error message in the query string `?error=...`.
file: ./content/docs/concepts/plugins.mdx
meta: {
"title": "Plugins",
"description": "Learn how to use plugins with Better Auth."
}
Plugins are a key part of Better Auth, they let you extend the base functionalities. You can use them to add new authentication methods, features, or customize behaviors.
Better Auth offers comes with many built-in plugins ready to use. Check the plugins section for details. You can also create your own plugins.
## Using a Plugin
Plugins can be a server-side plugin, a client-side plugin, or both.
To add a plugin on the server, include it in the `plugins` array in your auth configuration. The plugin will initialize with the provided options.
```ts title="server.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
plugins: [
// Add your plugins here
]
});
```
Client plugins are added when creating the client. Most plugin require both server and client plugins to work correctly.
The Better Auth auth client on the frontend uses the `createAuthClient` function provided by `better-auth/client`.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client";
const authClient = createAuthClient({
plugins: [
// Add your client plugins here
]
});
```
We recommend keeping the auth-client and your normal auth instance in separate files.
## Creating a Plugin
To get started, you'll need a server plugin.
Server plugins are the backbone of all plugins, and client plugins are there to provide an interface with frontend APIs to easily work with your server plugins.
If your server plugins has endpoints that needs to be called from the client, you'll also need to create a client plugin.
### What can a plugin do?
* Create custom `endpoint`s to perform any action you want.
* Extend database tables with custom `schemas`.
* Use a `middleware` to target a group of routes using it's route matcher, and run only when those routes are called through a request.
* Use `hooks` to target a specific route or request. And if you want to run the hook even if the endpoint is called directly.
* Use `onRequest` or `onResponse` if you want to do something that affects all requests or responses.
* Create custom `rate-limit` rule.
## Create a Server plugin
To create a server plugin you need to pass an object that satisfies the `BetterAuthPlugin` interface.
The only required property is `id`, which is a unique identifier for the plugin.
Both server and client plugins can use the same `id`.
```ts title="plugin.ts"
import type { BetterAuthPlugin } from "better-auth";
export const myPlugin = ()=>{
return {
id: "my-plugin",
} satisfies BetterAuthPlugin
}
```
You don't have to make the plugin a function, but it's recommended to do so. This way you can pass options to the plugin and it's consistent with the built-in plugins.
### Endpoints
To add endpoints to the server, you can pass `endpoints` which requires an object with the key being any `string` and the value being an `AuthEndpoint`.
To create an Auth Endpoint you'll need to import `createAuthEndpoint` from `better-auth`.
Better Auth uses wraps around another library called Better Call to create endpoints. Better call is a simple ts web framework made by the same team behind Better Auth.
```ts title="plugin.ts"
import { createAuthEndpoint } from "better-auth/api";
const myPlugin = ()=> {
return {
id: "my-plugin",
endpoints: {
getHelloWorld: createAuthEndpoint("/my-plugin/hello-world", {
method: "GET",
}, async(ctx) => {
return ctx.json({
message: "Hello World"
})
})
}
} satisfies BetterAuthPlugin
}
```
Create Auth endpoints wraps around `createEndpoint` from Better Call. Inside the `ctx` object, it'll provide another object called `context` that give you access better-auth specific contexts including `options`, `db`, `baseURL` and more.
**Context Object**
* `appName`: The name of the application. Defaults to "Better Auth".
* `options`: The options passed to the Better Auth instance.
* `tables`: Core tables definition. It is an object which has the table name as the key and the schema definition as the value.
* `baseURL`: the baseURL of the auth server. This includes the path. For example, if the server is running on `http://localhost:3000`, the baseURL will be `http://localhost:3000/api/auth` by default unless changed by the user.
* `session`: The session configuration. Includes `updateAge` and `expiresIn` values.
* `secret`: The secret key used for various purposes. This is defined by the user.
* `authCookie`: The default cookie configuration for core auth cookies.
* `logger`: The logger instance used by Better Auth.
* `db`: The Kysely instance used by Better Auth to interact with the database.
* `adapter`: This is the same as db but it give you `orm` like functions to interact with the database. (we recommend using this over `db` unless you need raw sql queries or for performance reasons)
* `internalAdapter`: These are internal db calls that are used by Better Auth. For example, you can use these calls to create a session instead of using `adapter` directly. `internalAdapter.createSession(userId)`
* `createAuthCookie`: This is a helper function that let's you get a cookie `name` and `options` for either to `set` or `get` cookies. It implements things like `__secure` prefix and `__host` prefix for cookies based on
For other properties, you can check the Better Call documentation and the source code .
**Rules for Endpoints**
* Makes sure you use kebab-case for the endpoint path
* Make sure to only use `POST` or `GET` methods for the endpoints.
* Any function that modifies a data should be a `POST` method.
* Any function that fetches data should be a `GET` method.
* Make sure to use the `createAuthEndpoint` function to create API endpoints.
* Make sure your paths are unique to avoid conflicts with other plugins. If you're using a common path, add the plugin name as a prefix to the path. (`/my-plugin/hello-world` instead of `/hello-world`.)
### Schema
You can define a database schema for your plugin by passing a `schema` object. The schema object should have the table name as the key and the schema definition as the value.
```ts title="plugin.ts"
import { BetterAuthPlugin } from "better-auth/plugins";
const myPlugin = ()=> {
return {
id: "my-plugin",
schema: {
myTable: {
fields: {
name: {
type: "string"
}
},
modelName: "myTable" // optional if you want to use a different name than the key
}
}
} satisfies BetterAuthPlugin
}
```
**Fields**
By default better-auth will create an `id` field for each table. You can add additional fields to the table by adding them to the `fields` object.
The key is the column name and the value is the column definition. The column definition can have the following properties:
`type`: The type of the filed. It can be `string`, `number`, `boolean`, `date`.
`required`: if the field should be required on a new record. (default: `false`)
`unique`: if the field should be unique. (default: `false`)
`reference`: if the field is a reference to another table. (default: `null`) It takes an object with the following properties:
* `model`: The table name to reference.
* `field`: The field name to reference.
* `onDelete`: The action to take when the referenced record is deleted. (default: `null`)
**Other Schema Properties**
`disableMigration`: if the table should not be migrated. (default: `false`)
```ts title="plugin.ts"
const myPlugin = (opts: PluginOptions)=>{
return {
id: "my-plugin",
schema: {
rateLimit: {
fields: {
key: {
type: "string",
},
},
disableMigration: opts.storage.provider !== "database", // [!code highlight]
},
},
} satisfies BetterAuthPlugin
}
```
if you add additional fields to a `user` or `session` table, the types will be inferred automatically on `getSession` and `signUpEmail` calls.
```ts title="plugin.ts"
const myPlugin = ()=>{
return {
id: "my-plugin",
schema: {
user: {
fields: {
age: {
type: "number",
},
},
},
},
} satisfies BetterAuthPlugin
}
```
This will add an `age` field to the `user` table and all `user` returning endpoints will include the `age` field and it'll be inferred properly by typescript.
Don't store sensitive information in `user` or `session` table. Crate a new table if you need to store sensitive information.
### Hooks
Hooks are used to run code before or after an action is performed, either from a client or directly on the server. You can add hooks to the server by passing a `hooks` object, which should contain `before` and `after` properties.
```ts title="plugin.ts"
import { createAuthMiddleware } from "better-auth/plugins";
const myPlugin = ()=>{
return {
id: "my-plugin",
hooks: {
before: [{
matcher: (context)=>{
return context.headers.get("x-my-header") === "my-value"
},
handler: createAuthMiddleware(async(ctx)=>{
//do something before the request
return {
context: ctx // if you want to modify the context
}
})
}],
after: [{
matcher: (context)=>{
return context.path === "/sign-up/email"
},
handler: async(ctx)=>{
return ctx.json({
message: "Hello World"
}) // if you want to modify the response
}
}]
}
} satisfies BetterAuthPlugin
}
```
### Middleware
You can add middleware to the server by passing a `middleware` array. This array should contain middleware objects, each with a `path` and a `middleware` property. Unlike hooks, middleware only runs on `api` requests from a client. If the endpoint is invoked directly, the middleware will not run.
The `path` can be either a string or a path matcher, using the same path-matching system as `better-call`.
If you throw an `APIError` from the middleware or returned a `Response` object, the request will be stopped and the response will be sent to the client.
```ts title="plugin.ts"
const myPlugin = ()=>{
return {
id: "my-plugin",
middleware: [
{
path: "/my-plugin/hello-world",
middleware: createAuthMiddleware(async(ctx)=>{
//do something
})
}
]
} satisfies BetterAuthPlugin
}
```
### On Request & On Response
Additional to middlewares, you can also hook into right before a request is made and right after a response is returned. This is mostly useful if you want to do something that affects all requests or responses.
#### On Request
The `onRequest` function is called right before the request is made. It takes two parameters: the `request` and the `context` object.
Here’s how it works:
* **Continue as Normal**: If you don't return anything, the request will proceed as usual.
* **Interrupt the Request**: To stop the request and send a response, return an object with a `response` property that contains a `Response` object.
* **Modify the Request**: You can also return a modified `request` object to change the request before it's sent.
```ts title="plugin.ts"
const myPlugin = ()=> {
return {
id: "my-plugin",
onRequest: async (request, context) => {
//do something
},
} satisfies BetterAuthPlugin
}
```
#### On Response
The `onResponse` function is executed immediately after a response is returned. It takes two parameters: the `response` and the `context` object.
Here’s how to use it:
* **Modify the Response**: You can return a modified response object to change the response before it is sent to the client.
* **Continue Normally**: If you don’t return anything, the response will be sent as is.
```ts title="plugin.ts"
const myPlugin = ()=>{
return {
id: "my-plugin",
onResponse: async (response, context) => {
//do something
},
} satisfies BetterAuthPlugin
}
```
### Rate Limit
You can define custom rate limit rules for your plugin by passing a `rateLimit` array. The rate limit array should contain an array of rate limit objects.
```ts title="plugin.ts"
const myPlugin = ()=>{
return {
id: "my-plugin",
rateLimit: [
{
pathMatcher: (path)=>{
return path === "/my-plugin/hello-world"
},
limit: 10,
window: 60,
}
]
} satisfies BetterAuthPlugin
}
```
### Server-plugin helper functions
Some additional helper functions for creating server plugins.
#### `getSessionFromCtx`
Allows you to get the client's session data by passing the auth middleware's `context`.
```ts title="plugin.ts"
import { createAuthMiddleware } from "better-auth/plugins";
const myPlugin = {
id: "my-plugin",
hooks: {
before: [{
matcher: (context)=>{
return context.headers.get("x-my-header") === "my-value"
},
handler: createAuthMiddleware(async (ctx) => {
const session = await getSessionFromCtx(ctx);
//do something with the client's session.
return {
context: ctx
}
})
}],
}
} satisfies BetterAuthPlugin
```
#### `sessionMiddleware`
A middleware that checks if the client has a valid session. If the client has a valid session, it'll add the session data to the context object.
```ts title="plugin.ts"
import { createAuthMiddleware } from "better-auth/plugins";
import { sessionMiddleware } from "better-auth/api";
const myPlugin = ()=>{
return {
id: "my-plugin",
endpoints: {
getHelloWorld: createAuthEndpoint("/my-plugin/hello-world", {
method: "GET",
use: [sessionMiddleware], // [!code highlight]
}, async(ctx) => {
const session = ctx.context.session;
return ctx.json({
message: "Hello World"
})
})
}
} satisfies BetterAuthPlugin
}
```
## Creating a client plugin
If your endpoints needs to be called from the client, you'll need to also create a client plugin. Better Auth clients can infer the endpoints from the server plugins. You can also add additional client side logic.
```ts title="client-plugin.ts"
import type { BetterAuthClientPlugin } from "better-auth";
export const myPluginClient = ()=>{
return {
id: "my-plugin",
} satisfies BetterAuthClientPlugin
}
```
### Endpoint Interface
Endpoints are inferred from the server plugin by adding a `$InferServerPlugin` key to the client plugin.
The client infers the `path` as an object and converts kebab-case to camelCase. For example, `/my-plugin/hello-world` becomes `myPlugin.helloWorld`.
```ts title="client-plugin.ts"
import type { BetterAuthClientPlugin } from "better-auth/client";
import type { myPlugin } from "./plugin";
const myPluginClient = ()=> {
return {
id: "my-plugin",
$InferServerPlugin: {} as ReturnType,
} satisfies BetterAuthClientPlugin
}
```
### Get actions
If you need to add additional methods or what not to the client you can use the `getActions` function. This function is called with the `fetch` function from the client.
Better Auth uses Better fetch to make requests. Better fetch is a simple fetch wrapper made by the same author of Better Auth.
```ts title="client-plugin.ts"
import type { BetterAuthClientPlugin } from "better-auth/client";
import type { myPlugin } from "./plugin";
import type { BetterFetchOption } from "@better-fetch/fetch";
const myPluginClient = {
id: "my-plugin",
$InferServerPlugin: {} as ReturnType,
getActions: ($fetch)=>{
return {
myCustomAction: async (data: {
foo: string,
}, fetchOptions?: BetterFetchOption)=>{
const res = $fetch("/custom/action", {
method: "POST",
body: {
foo: data.foo
},
...fetchOptions
})
return res
}
}
}
} satisfies BetterAuthClientPlugin
```
As a general guideline, ensure that each function accepts only one argument, with an optional second argument for fetchOptions to allow users to pass additional options to the fetch call. The function should return an object containing data and error keys.
If your use case involves actions beyond API calls, feel free to deviate from this rule.
### Get Atoms
This is only useful if you want to provide `hooks` like `useSession`.
Get atoms is called with the `fetch` function from better fetch and it should return an object with the atoms. The atoms should be created using nanostores. The atoms will be resolved by each framework `useStore` hook provided by nanostores.
```ts title="client-plugin.ts"
import { atom } from "nanostores";
import type { BetterAuthClientPlugin } from "better-auth/client";
const myPluginClient = {
id: "my-plugin",
$InferServerPlugin: {} as ReturnType,
getAtoms: ($fetch)=>{
const myAtom = atom()
return {
myAtom
}
}
} satisfies BetterAuthClientPlugin
```
See built in plugins for examples of how to use atoms properly.
### Path methods
by default, inferred paths use `GET` method if they don't require a body and `POST` if they do. You can override this by passing a `pathMethods` object. The key should be the path and the value should be the method ("POST" | "GET").
```ts title="client-plugin.ts"
import type { BetterAuthClientPlugin } from "better-auth/client";
import type { myPlugin } from "./plugin";
const myPluginClient = {
id: "my-plugin",
$InferServerPlugin: {} as ReturnType,
pathMethods: {
"/my-plugin/hello-world": "POST"
}
} satisfies BetterAuthClientPlugin
```
### Fetch plugins
If you need to use better fetch plugins you can pass them to the `fetchPlugins` array. You can read more about better fetch plugins in the better fetch documentation.
### Atom Listeners
This is only useful if you want to provide `hooks` like `useSession` and you want to listen to atoms and re-evaluate them when they change.
You can see how this is used in the built-in plugins.
file: ./content/docs/concepts/rate-limit.mdx
meta: {
"title": "Rate Limit",
"description": "How to limit the number of requests a user can make to the server in a given time period."
}
Better Auth includes a built-in rate limiter to help manage traffic and prevent abuse. By default, in production mode, the rate limiter is set to:
* Window: 60 seconds
* Max Requests: 100 requests
You can easily customize these settings by passing the rateLimit object to the betterAuth function.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
rateLimit: {
window: 10, // time window in seconds
max: 100, // max requests in the window
},
})
```
In addition to the default settings, Better Auth provides custom rules for specific paths. For example:
* `/sign-in/email`: Is limited to 3 requests within 10 seconds.
In addition, plugins also define custom rules for specific paths. For example, `twoFactor` plugin has custom rules:
* `/two-factor/verify`: Is limited to 3 requests within 10 seconds.
These custom rules ensure that sensitive operations are protected with stricter limits.
## Configuring Rate Limit
### Rate Limit Window
```ts title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
//...other options
rateLimit: {
window: 60, // time window in seconds
max: 100, // max requests in the window
},
})
```
You can also pass custom rules for specific paths.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
//...other options
rateLimit: {
window: 60, // time window in seconds
max: 100, // max requests in the window
customRules: {
"/sign-in/email": {
window: 10,
max: 3,
},
"/two-factor/*": async (request)=> {
// custom function to return rate limit window and max
return {
window: 10,
max: 3,
}
}
},
},
})
```
### Storage
By default, rate limit data is stored in memory, which may not be suitable for many use cases, particularly in serverless environments. To address this, you can use a database, secondary storage, or custom storage for storing rate limit data.
**Using Database**
```ts title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
//...other options
rateLimit: {
storage: "database",
modelName: "rateLimit", //optional by default "rateLimit" is used
},
})
```
Make sure to run `migrate` to create the rate limit table in your database.
```bash
npx @better-auth/cli migrate
```
**Using Secondary Storage**
If a [Secondary Storage](/docs/concepts/database#secondary-storage) has been configured you can use that to store rate limit data.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
//...other options
rateLimit: {
storage: "secondary-storage"
},
})
```
**Custom Storage**
If none of the above solutions suits your use case you can implement a `customStorage`.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
//...other options
rateLimit: {
customStorage: {
get: async (key) => {
// get rate limit data
},
set: async (key, value) => {
// set rate limit data
},
},
},
})
```
## Handling Rate Limit Errors
When a request exceeds the rate limit, Better Auth returns the following header:
* `X-Retry-After`: The number of seconds until the user can make another request.
To handle rate limit errors on the client side, you can manage them either globally or on a per-request basis. Since Better Auth clients wrap over Better Fetch, you can pass `fetchOptions` to handle rate limit errors
**Global Handling**
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client";
export const authClient = createAuthClient({
fetchOptions: {
onError: async (context) => {
const { response } = context;
if (response.status === 429) {
const retryAfter = response.headers.get("X-Retry-After");
console.log(`Rate limit exceeded. Retry after ${retryAfter} seconds`);
}
},
}
})
```
**Per Request Handling**
```ts title="auth-client.ts"
import { client } from "./client";
await authClient.signIn.email({
fetchOptions: {
onError: async (context) => {
const { response } = context;
if (response.status === 429) {
const retryAfter = response.headers.get("X-Retry-After");
console.log(`Rate limit exceeded. Retry after ${retryAfter} seconds`);
}
},
}
})
```
### Schema
If you are using a database to store rate limit data you need this schema:
Table Name: `rateLimit`
file: ./content/docs/concepts/session-management.mdx
meta: {
"title": "Session Management",
"description": "Better Auth session management."
}
Better Auth manages session using a traditional cookie-based session management. The session is stored in a cookie and is sent to the server on every request. The server then verifies the session and returns the user data if the session is valid.
## Session table
The session table stores the session data. The session table has the following fields:
* `id`: The session token. Which is also used as the session cookie.
* `userId`: The user id of the user.
* `expiresAt`: The expiration date of the session.
* `ipAddress`: The IP address of the user.
* `userAgent`: The user agent of the user. It stores the user agent header from the request.
## Session Expiration
The session expires after 7 days by default. But whenever the session is used, and the `updateAge` is reached the session expiration is updated to the current time plus the `expiresIn` value.
You can change both the `expiresIn` and `updateAge` values by passing the `session` object to the `auth` configuration.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
//... other config options
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24 // 1 day (every 1 day the session expiration is updated)
}
})
```
## Session Freshness
Some endpoints in Better Auth require the session to be **fresh**. A session is considered fresh if its `createdAt` is within the `freshAge` limit. By default, the `freshAge` is set to **1 day** (60 \* 60 \* 24).
You can customize the `freshAge` value by passing a `session` object in the `auth` configuration:
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
//... other config options
session: {
freshAge: 60 * 5 // 5 minutes (the session is fresh if created within the last 5 minutes)
}
})
```
To **disable the freshness check**, set `freshAge` to `0`:
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
//... other config options
session: {
freshAge: 0 // Disable freshness check
}
})
```
## Session Management
Better Auth provides a set of functions to manage sessions.
### Get Session
The `getSession` function retrieves the current active session.
```ts client="client.ts"
import { authClient } from "@/lib/client"
const { data: session } = await authClient.getSession()
```
To learn how to customize the session response check the [Customizing Session Response](#customizing-session-response) section.
### Use Session
The `useSession` action provides a reactive way to access the current session.
```ts client="client.ts"
import { authClient } from "@/lib/client"
const { data: session } = authClient.useSession()
```
### List Sessions
The `listSessions` function returns a list of sessions that are active for the user.
```ts title="auth-client.ts"
import { authClient } from "@/lib/client"
const sessions = await authClient.listSessions()
```
### Revoke Session
When a user signs out of a device, the session is automatically ended. However, you can also end a session manually from any device the user is signed into.
To end a session, use the `revokeSession` function. Just pass the session token as a parameter.
```ts title="auth-client.ts"
await authClient.revokeSession({
token: "session-token"
})
```
### Revoke Other Sessions
To revoke all other sessions except the current session, you can use the `revokeOtherSessions` function.
```ts title="auth-client.ts"
await authClient.revokeOtherSessions()
```
### Revoke All Sessions
To revoke all sessions, you can use the `revokeSessions` function.
```ts title="auth-client.ts"
await authClient.revokeSessions()
```
### Revoking Sessions on Password Change
You can revoke all sessions when the user changes their password by passing `revokeOtherSessions` as true on `changePassword` function.
```ts title="auth.ts"
await authClient.changePassword({
newPassword: newPassword,
currentPassword: currentPassword,
revokeOtherSessions: true,
})
```
## Session Caching
### Cookie Cache
Calling your database every time `useSession` or `getSession` invoked isn’t ideal, especially if sessions don’t change frequently. Cookie caching handles this by storing session data in a short-lived, signed cookie—similar to how JWT access tokens are used with refresh tokens.
When cookie caching is enabled, the server can check session validity from the cookie itself instead of hitting the database each time. The cookie is signed to prevent tampering, and a short `maxAge` ensures that the session data gets refreshed regularly. If a session is revoked or expires, the cookie will be invalidated automatically.
To turn on cookie caching, just set `session.cookieCache` in your auth config:
```ts title="auth.ts"
const auth = new BetterAuth({
session: {
cookieCache: {
enabled: true,
maxAge: 5 * 60 // Cache duration in seconds
}
}
});
```
If you want to disable returning from the cookie cache when fetching the session, you can pass `disableCookieCache:true`
```ts title="auth-client.ts"
const session = await authClient.getSession({ query: {
disableCookieCache: true
}})
```
or on the server
```ts title="server.ts"
auth.api.getSession({
query: {
disableCookieCache: true,
},
headers: req.headers, // pass the headers
});
```
## Customizing Session Response
When you call `getSession` or `useSession`, the session data is returned as a `user` and `session` object. You can customize this response using the `customSession` plugin.
```ts title="auth.ts"
import { customSession } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
customSession(async ({ user, session }) => {
const roles = findUserRoles(session.session.userId);
return {
roles,
user: {
...user,
newField: "newField",
},
session
};
}),
],
});
```
This will add `roles` and `user.newField` to the session response.
**Infer on the Client**
```ts title="auth-client.ts"
import { customSessionClient } from "better-auth/client/plugins";
import type { auth } from "@/lib/auth"; // Import the auth instance as a type
const authClient = createAuthClient({
plugins: [customSessionClient()],
});
const { data } = await authClient.useSession();
const { data: sessionData } = await authClient.getSession();
// data.roles
// data.user.newField
```
**Some Caveats**:
* The passed `session` object to the callback does not infer fields added by plugins.
However, as a workaround, you can pull up your auth options and pass it to the plugin to infer the fields.
```ts
import { betterAuth, BetterAuthOptions } from "better-auth";
const options = {
//...config options
plugins: [
//...plugins
]
} satisfies BetterAuthOptions;
export const auth = betterAuth({
...options,
plugins: [
...(options.plugins ?? []),
customSession(async ({ user, session }) => {
// now both user and session will infer the fields added by plugins and your custom fields
return {
user,
session
}
}, options), // pass options here // [!code highlight]
]
})
```
* If you cannot use the `auth` instance as a type, inference will not work on the client.
* Session caching, including secondary storage or cookie cache, does not include custom fields. Each time the session is fetched, your custom session function will be called.
file: ./content/docs/concepts/typescript.mdx
meta: {
"title": "TypeScript",
"description": "Better Auth TypeScript integration."
}
Better Auth is designed to be type-safe. Both the client and server are built with TypeScript, allowing you to easily infer types.
## Typescript Config
### Strict Mode
Better Auth is designed to work with TypeScript's strict mode. We recommend enabling strict mode in your TypeScript config file:
```json title="tsconfig.json"
{
"compilerOptions": {
"strict": true
}
}
```
if you can't set `strict` to `true`, you can enable `strictNullChecks`:
```json title="tsconfig.json"
{
"compilerOptions": {
"strictNullChecks": true,
}
}
```
## Inferring Types
Both the client SDK and the server offer types that can be inferred using the `$Infer` property. Plugins can extend base types like `User` and `Session`, and you can use `$Infer` to infer these types. Additionally, plugins can provide extra types that can also be inferred through `$Infer`.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
const authClient = createAuthClient()
export type Session = typeof authClient.$Infer.Session
```
The `Session` type includes both `session` and `user` properties. The user property represents the user object type, and the `session` property represents the `session` object type.
You can also infer types on the server side.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import Database from "better-sqlite3"
export const auth = betterAuth({
database: new Database("database.db")
})
type Session = typeof auth.$Infer.Session
```
## Additional Fields
Better Auth allows you to add additional fields to the user and session objects. All additional fields are properly inferred and available on the server and client side.
```ts
import { betterAuth } from "better-auth"
import Database from "better-sqlite3"
export const auth = betterAuth({
database: new Database("database.db"),
user: {
additionalFields: {
role: {
type: "string"
}
}
}
})
type Session = typeof auth.$Infer.Session
```
In the example above, we added a `role` field to the user object. This field is now available on the `Session` type.
### Inferring Additional Fields on Client
To make sure proper type inference for additional fields on the client side, you need to inform the client about these fields. There are two approaches to achieve this, depending on your project structure:
1. For Monorepo or Single-Project Setups
If your server and client code reside in the same project, you can use the `inferAdditionalFields` plugin to automatically infer the additional fields from your server configuration.
```ts
import { inferAdditionalFields } from "better-auth/client/plugins";
import { createAuthClient } from "better-auth/react";
import type { auth } from "./auth";
export const authClient = createAuthClient({
plugins: [inferAdditionalFields()],
});
```
2. For Separate Client-Server Projects
If your client and server are in separate projects, you'll need to manually specify the additional fields when creating the auth client.
```ts
import type { auth } from "./auth";
import { inferAdditionalFields } from "better-auth/client/plugins";
export const authClient = createAuthClient({
plugins: [inferAdditionalFields({
user: {
role: {
type: "string"
}
}
})],
});
```
file: ./content/docs/concepts/users-accounts.mdx
meta: {
"title": "User & Accounts",
"description": "User and account management."
}
Beyond authenticating users, Better Auth also provides a set of methods to manage users. This includes, updating user information, changing passwords, and more.
## User table
The user table stores the user data. The user table has the following fields:
* `id`: The user id.
* `email`: The email of the user.
* `name`: The name of the user.
* `image`: The image of the user.
* `createdAt`: The creation date of the user.
* `updatedAt`: The last update date of the user.
The user table can be extended using [additional fields](/docs/concepts/database#extending-core-schema) or by plugins to store additional data.
## Update User
### Update User Information
To update user information, you can use the `updateUser` function provided by the client. The `updateUser` function takes an object with the following properties:
```ts
await authClient.updateUser({
image: "https://example.com/image.jpg",
name: "John Doe",
})
```
### Change Email
To allow users to change their email, first enable the `changeEmail` feature, which is disabled by default. Set `changeEmail.enabled` to `true`:
```ts
export const auth = betterAuth({
user: {
changeEmail: {
enabled: true,
}
}
})
```
For users with a verified email, provide the `sendChangeEmailVerification` function. This function triggers when a user changes their email, sending a verification email with a URL and token. If the current email isn't verified, the change happens immediately without verification.
```ts
export const auth = betterAuth({
user: {
changeEmail: {
enabled: true,
sendChangeEmailVerification: async ({ user, newEmail, url, token }, request) => {
await sendEmail({
to: user.email, // verification email must be sent to the current user email to approve the change
subject: 'Approve email change',
text: `Click the link to approve the change: ${url}`
})
}
}
}
})
```
Once enabled, use the `changeEmail` function on the client to update a user’s email. The user must verify their current email before changing it.
```ts
await authClient.changeEmail({
newEmail: "new-email@email.com",
callbackURL: "/dashboard", //to redirect after verification
});
```
After verification, the new email is updated in the user table, and a confirmation is sent to the new address.
If the current email is unverified, the new email is updated without the verification step.
### Change Password
Password of a user isn't stored in the user table. Instead, it's stored in the account table. To change the password of a user, you can use the `changePassword` function provided by the client. The `changePassword` function takes an object with the following properties:
```ts
await authClient.changePassword({
newPassword: "newPassword123",
currentPassword: "oldPassword123",
revokeOtherSessions: true, // revoke all other sessions the user is signed into
});
```
### Set Password
If a user was registered using OAuth or other providers, they won't have a password or a credential account. In this case, you can use the `setPassword` action to set a password for the user. For security reasons, this function can only be called from the server. We recommend having users go through a 'forgot password' flow to set a password for their account.
```ts
await auth.api.setPassword({
body: { newPassword: "password" },
headers: //
});
```
## Delete User
Better Auth provides a utility to hard delete a user from your database. It's disabled by default, but you can enable it easily by passing `enabled:true`
```ts
export const auth = betterAuth({
//...other config
user: {
deleteUser: { // [!code highlight]
enabled: true // [!code highlight]
} // [!code highlight]
}
})
```
Once enabled, you can call `authClient.deleteUser` to permanently delete user data from your database.
### Adding Verification Before Deletion
For added security, you’ll likely want to confirm the user’s intent before deleting their account. A common approach is to send a verification email. Better Auth provides a `sendDeleteAccountVerification` utility for this purpose.
Here’s how you can set it up:
```ts
export const auth = betterAuth({
user: {
deleteUser: {
enabled: true,
sendDeleteAccountVerification: async (
{
user, // The user object
url, // The auto-generated URL for deletion
token // The verification token (can be used to generate custom URL)
},
request // The original request object (optional)
) => {
// Your email sending logic here
// Example: sendEmail(data.user.email, "Verify Deletion", data.url);
},
},
},
});
```
**How callback verification works:**
* **Callback URL**: The url provided in `sendDeleteAccountVerification` is a pre-generated link that deletes the user data when accessed.
```ts title="delete-user.ts"
await authClient.deleteUser({
callbackURL: "/goodbye" // you can provide a callback URL to redirect after deletion
});
```
* **Authentication Check**: The user must be signed in to the account they’re attempting to delete.
If they aren’t signed in, the deletion process will fail.
If you have sent a custom URL, you can use the `deleteUser` method with the token to delete the user.
```ts title="delete-user.ts"
await authClient.deleteUser({
token
});
```
### Authentication Requirements
To delete a user, the user must meet one of the following requirements:
1. A valid password
if the user has a password, they can delete their account by providing the password.
```ts title="delete-user.ts"
await authClient.deleteUser({
password: "password"
});
```
2. Fresh session
The user must have a `fresh` session token, meaning the user must have signed in recently. This is checked if the password is not provided.
By default `session.freshAge` is set to `60 * 60 * 24` (1 day). You can change this value by passing the `session` object to the `auth` configuration. If it is set to `0`, the freshness check is disabled.
```ts title="delete-user.ts"
await authClient.deleteUser();
```
3. The user must provide a token generated by the `sendDeleteAccountVerification` callback.
```ts title="delete-user.ts"
await authClient.deleteUser({
token
});
```
### Callbacks
**beforeDelete**: This callback is called before the user is deleted. You can use this callback to perform any cleanup or additional checks before deleting the user.
```ts title="auth.ts"
export const auth = betterAuth({
user: {
deleteUser: {
enabled: true,
beforeDelete: async (user) => {
// Perform any cleanup or additional checks here
},
},
},
});
```
you can also throw `APIError` to interrupt the deletion process.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { APIError } from "better-auth/api";
export const auth = betterAuth({
user: {
deleteUser: {
enabled: true,
beforeDelete: async (user, request) => {
if (user.email.includes("admin")) {
throw new APIError("BAD_REQUEST", {
message: "Admin accounts can't be deleted",
});
}
},
},
},
});
```
**afterDelete**: This callback is called after the user is deleted. You can use this callback to perform any cleanup or additional actions after the user is deleted.
```ts title="auth.ts"
export const auth = betterAuth({
user: {
deleteUser: {
enabled: true,
afterDelete: async (user, request) => {
// Perform any cleanup or additional actions here
},
},
},
});
```
## Accounts
Better Auth supports multiple authentication methods. Each authentication method is called a provider. For example, email and password authentication is a provider, Google authentication is a provider, etc.
When a user signs in using a provider, an account is created for the user. The account stores the authentication data returned by the provider. This data includes the access token, refresh token, and other information returned by the provider.
### Account table
The account table stores the authentication data of the user. The account table has the following fields:
* `id`: The unique identifier of the account.
* `userId`: The id of the user.
* `accountId`: The id of the account provided by the OAuth provider. (optional)
* `providerId`: The id of the provider. (optional)
* `accessToken`: The access token of the account. Returned by the provider. (optional)
* `refreshToken`: The refresh token of the account. Returned by the provider. (optional)
* `expiresAt`: The time when the access token expires. (optional)
* `password`: The password of the account. Mainly used for email and password authentication. (optional)
### List User Accounts
To list user accounts you can use `client.user.listAccounts` method. Which will return all accounts associated with a user.
```ts
const accounts = await authClient.listAccounts();
```
### Account Linking
Account linking enables users to associate multiple authentication methods with a single account. With Better Auth, users can connect additional social sign-ons or OAuth providers to their existing accounts if the provider confirms the user's email as verified.
If account linking is disabled, no accounts can be linked, regardless of the provider or email verification status.
```ts title="auth.ts"
const auth = new BetterAuth({
account: {
accountLinking: {
enabled: true,
}
},
});
```
#### Forced Linking
You can specify a list of "trusted providers." When a user logs in using a trusted provider, their account will be automatically linked even if the provider doesn’t confirm the email verification status. Use this with caution as it may increase the risk of account takeover.
```ts title="auth.ts"
const auth = new BetterAuth({
account: {
accountLinking: {
enabled: true,
trustedProviders: ["google", "github"]
}
},
});
```
#### Manually Linking Accounts
Users already signed in can manually link their account to additional social providers or credential-based accounts.
* **Linking Social Accounts:** Use the `user.linkSocial` method on the client to link a social provider to the user's account.
```ts
await authClient.linkSocial({
provider: "google", // Provider to link
callbackURL: "/callback" // Callback URL after linking completes
});
```
If you want your users to be able to link a social account with a different email address than the user, or if you want to use a provider that does not return email addresses, you will need to enable this in the account linking settings.
```ts title="auth.ts"
const auth = betterAuth({
account: {
accountLinking: {
allowDifferentEmails: true
}
},
});
```
* **Linking Credential-Based Accounts:** To link a credential-based account (e.g., email and password), users can initiate a "forgot password" flow, or you can call the `setPassword` method on the server.
```ts
await auth.api.setPassword({
headers: /* headers containing the user's session token */,
password: /* new password */
});
```
`setPassword` can't be called from the client for security reasons.
### Account Unlinking
You can unlink a user account by providing a `providerId`.
```ts
await authClient.unlinkAccount({
providerId: "google"
});
```
If the `providerId` doesn't exist, it will throw an error. Additionally, if the user only has one account, the unlinking process will fail to prevent account lockout.
file: ./content/docs/contribute/areas-to-contribute.mdx
meta: {
"title": "Areas To Contribute",
"description": "Areas to contribute to BetterAuth"
}
## Working on Issues
The easiest way to start contributing is by tackling existing issues. Here's how:
1. Check our [github issues](https://github.com/better-auth/better-auth/issues) for issues labeled `good first issue`, `help wanted` or `question`
2. Comment on the issue you'd like to work on to avoid duplicate efforts
3. If you've found a bug or have a feature request not listed, please create a new issue:
* For bugs: Include steps to reproduce, expected vs actual behavior
* For features: Describe the feature and its use cases
## Framework Integrations
Better Auth aims to support as many frameworks as possible in the JavaScript ecosystem. Here's how you can help expand our framework support:
### Adding New Framework Support
1. Start with documentation for basic integration
2. Focus on framework-agnostic solutions where possible
3. Keep integrations minimal and maintainable
### Integration Guidelines
* Prefer framework-agnostic solutions over framework-specific ones
* Keep server-side integrations as thin as possible
* All integrations currently live in the main package (packages/better-auth)
* Focus on reducing implementation friction while maintaining flexibility
### Current Structure
We currently maintain all integrations (frameworks, plugins, utilities) in a single package for simplicity. As the project grows, we may consider splitting these into separate packages.
## Plugin Development
Want to extend Better Auth's functionality? Here's our plugin contribution process:
### Core Plugins
1. **Open an Issue First**: Describe your plugin idea
2. **Get Approval**: Wait for team feedback before starting
3. **Development**: Follow our plugin development guide
### Community Plugins
* Feel free to develop and publish plugins independently
* Use our Plugin docs as a reference
* Consider sharing your plugin in our community showcase channel.
## Core Feature Development
Contributing to core features requires careful coordination:
### Process
1. **Open an Issue**: Describe the feature and its use cases
2. **Discussion**: Get feedback from the team and community
3. **Planning**: Outline implementation approach
4. **Development**: Create a draft PR early for guidance
### Best Practices
* Start with a minimal viable implementation
* Include tests and documentation
* Keep backwards compatibility in mind
* Follow our coding standards
## Security Contributions
Security is our top priority. If you discover a security vulnerability:
1. **DO NOT** create a public issue
2. Send details to [security@better-auth.com](mailto:security@better-auth.com)
3. Include:
* Description of the vulnerability
* Steps to reproduce
* Potential impact
* Suggested fix (if any)
Learn more about our [security issues documentation](https://better-auth.com/docs/contribute/security-issues).
## Other Ways to Contribute
* Improve error messages
* Add test cases
* Enhance documentation
* Share examples
* Report performance issues
* Suggest optimizations
Remember, all contributions, no matter how small, are valuable to the project. If you're unsure about anything, don't hesitate to ask in our Discord server.
Let's make authentication better, together! 🚀
file: ./content/docs/contribute/database-adapters.mdx
meta: {
"title": "Database Adapters",
"description": "Adding a new database adapter to BetterAuth"
}
WORKIN IN PROGRESS.
file: ./content/docs/contribute/documenting.mdx
meta: {
"title": "Documenting",
"description": "Documentation Guide for Better Auth"
}
## Overview
Better Auth uses [Fumadocs](https://fumadocs.vercel.app/) with MDX for documentation. This page explains how to add and update documentation effectively.
## Project Structure
## Writing Documentation
### Creating New Pages
1. Add your MDX file in `docs/content/docs/.mdx`:
#### File Name Convention \* The file name should be in the format of
`some-feature.mdx`. (Kebab-case) \* The file name represents the URL route to
that page. For example, if the file name is `some-feature.mdx`, the URL route
will be `/docs/some-feature`. \* Place the file in their respective category
folder. For example, if the file is in the `guides` category, place it in the
`/guides` folder.
```mdx
---
title: Your Feature
description: Description of your feature
---
# Your Feature
Introduction to your feature here...
```
2. Update the sidebar in `docs/components/sidebar-content.tsx`:
To find out what icon you would like to use for the sidebar, you can check out
the Lucide Icons library.
```tsx
export const contents: Content[] = {
items: [
{
// Existing items...
},
{
title: "Your Section",
icon: ,
items: [
{
title: "Your Feature",
icon: ,
href: "/docs/your-feature",
},
],
},
],
};
```
***
## MDX Guidelines
### Basic Formatting
```mdx
# Main Title
## Section
This is a paragraph with **bold** and _italic_ text.
### Subsection
- List item 1
- List item 2
1. Order list item 1
2. Order list item 2
```
### Components
```mdx
Important information goes hereSome warningSome infoSome error
```
Important information goes hereSome warningSome infoSome error
```mdx
```
```mdx
Link to Introduction
```
Link to Introduction
```mdx
Some content!
Some content!
```
Some content!
Some content!
```mdx
## Step 1
Hello world!
## Step 2
Hello world!
```
Step 1Hello world!Step 2Hello world!
```mdx
Javascript is weirdRust is fast
```
Javascript is weirdRust is fast
### Code Blocks
Use language-specific syntax highlighting:
````mdx
```typescript
import { BetterAuth } from "better-auth";
const auth = new BetterAuth({
// configuration
});
```
````
Optionally, you can also specify a file name for the code block:
````mdx
```typescript title="auth.ts"
import { BetterAuth } from "better-auth";
const auth = new BetterAuth({
// configuration
});
```
````
You can also highlight specific lines in a codeblock:
````mdx
```typescript title="auth.ts"
import { BetterAuth } from "better-auth";
const auth = new BetterAuth({ // [!codeㅤhighlight]
// configuration // [!codeㅤhighlight]
}); // [!codeㅤhighlight]
```
````
The result:
```typescript title="auth.ts"
import { BetterAuth } from "better-auth";
const auth = new BetterAuth({// [!code highlight]
// configuration // [!code highlight]
}); // [!code highlight]
```
***
## Best Practices
### Do's
* Keep documentation up-to-date with code changes
* Use clear, concise language
* Include practical examples
* Document error cases and edge conditions
* Use proper headings hierarchy
* Include cross-references to related docs
### Don'ts
* Don't repeat information unnecessarily
* Don't leave outdated examples
* Don't use complex jargon without explanation
* Don't skip documenting breaking changes
* Don't leave TODO comments in published docs
## Local Development
```bash
# Install dependencies
pnpm install
# Start docs development server
pnpm -F docs dev
```
## Document Types
### 1. Guides
* Step-by-step instructions
* Tutorial-style content
### 2. Reference Documentation
* API documentation
* Configuration options
* Type definitions
### 3. Examples
* Code snippets
* Complete examples
* Use cases
## Contributing to Documentation
1. **Find the Right File**
* Navigate to `docs/content/docs/`
* Locate the relevant MDX file or create a new one
2. **Make Your Changes**
* Update or add content
* Follow the MDX formatting guidelines
* Include examples where appropriate
3. **Test Locally**
* Run the development server
* Check your changes
* Verify links and navigation
4. **Submit Changes**
* Create a pull request
* Include a clear description of documentation changes
* Request review from documentation maintainers
## Getting Help
Need assistance with documentation?
* Join our Discord server and ask for help!
* Open an issue with the `documentation` label
Remember: Good documentation is crucial for Better Auth's success. Thank you for helping improve it! 📚
file: ./content/docs/contribute/getting-started.mdx
meta: {
"title": "Contributing to BetterAuth",
"description": "Getting started to contribute to BetterAuth"
}
Hello and welcome! We're thrilled that you're interested in contributing to Better Auth. Whether you're fixing bugs, adding new features, improving documentation, or just sharing ideas, your contribution helps make Better Auth better for everyone.
## Getting Started
Before diving in, here are a few important resources:
* Take a look at our existing issues and pull requests
* Join our community discussions in Discord
## Development Setup
To get started with development:
Make sure you have Node.JS{" "}
installed, preferably on LTS.
### 1. Fork the repository
Visit [https://github.com/better-auth/better-auth](https://github.com/better-auth/better-auth)
Click the "Fork" button in the top right.
### 2. Clone your fork
```bash
# Replace YOUR-USERNAME with your GitHub username
git clone https://github.com/YOUR-USERNAME/better-auth.git
cd better-auth
```
### 3. Install dependencies
Make sure you have pnpm installed!
```bash
pnpm install
```
### 4. Prepare ENV files
Copy the example env file to create your new `.env` file.
```bash
cp -n ./docs/.env.example ./docs/.env
```
## Making changes
Once you have an idea of what you want to contribute, you can start making changes. Here are some steps to get started:
### 1. Create a new branch
```bash
# Make sure you're on main
git checkout main
# Pull latest changes
git pull upstream main
# Create and switch to a new branch
git checkout -b feature/your-feature-name
```
### 2. Start development server
Start the development server:
```bash
pnpm dev
```
To start the docs server:
```bash
pnpm -F docs dev
```
### 3. Make Your Changes
* Make your changes to the codebase.
* Write tests if needed. (Read more about testing here)
* Update documentation. (Read more about documenting here)
## Best Practices
* Write clear commit messages
* Update documentation to reflect your changes
* Add tests for new features
* Follow our coding standards
* Keep pull requests focused on a single change
## Need Help?
Don't hesitate to ask for help! You can:
* Open an issue with questions
* Join our community discussions
* Reach out to project maintainers
Together, we can make Better Auth an even better authentication solution. Thank you for being part of our community!
Your contributions are greatly appreciated, and we look forward to collaborating with you!
file: ./content/docs/contribute/security-issues.mdx
meta: {
"title": "Security Policy and Disclosure Guidelines",
"description": "Security Policy and Disclosure Guidelines in BetterAuth"
}
## Our Security Commitment
At Better Auth, security is our highest priority. We take all security concerns seriously and appreciate the efforts of security researchers and our community in responsibly disclosing potential vulnerabilities.
## Reporting a Vulnerability
### Preferred Method
Please report security issues by emailing:
```
security@better-auth.com
```
### What to Include
When reporting a security issue, please include:
1. **Description**
* Clear explanation of the vulnerability
* Affected versions/components
* Type of vulnerability (e.g., XSS, CSRF, Authentication Bypass)
2. **Reproduction Steps**
* Detailed steps to reproduce the vulnerability
* Any required setup or configuration
* Code samples if applicable
* Example payload if relevant
3. **Impact Assessment**
* Potential security impact
* What an attacker might be able to accomplish
* Affected user groups or data
4. **Supporting Materials**
* Screenshots or videos (if applicable)
* Proof of concept code (if available)
* Related references or CVEs
5. **Mitigation Suggestions**
* Proposed fixes or workarounds
* Recommended security controls
## Our Response Process
1. **Initial Response**
* Acknowledgment within 24 to 48 hours
* Case number assignment
* Initial severity assessment
2. **Investigation**
* Technical review of the report
* Impact analysis
* Verification of reproduction steps
* Development of fix strategy
3. **Resolution Timeline**
* Critical vulnerabilities: 24-48 hours
* High severity: 1 week
* Medium severity: 2 weeks
* Low severity: Next release cycle
4. **Communication**
* Regular updates on fix progress
* Notification when fix is ready
* Coordination on disclosure timeline
## Disclosure Policy
1. **Responsible Disclosure**
* No public disclosure before fix implementation
* Coordinated release of security advisories
* Credit given to reporters in security advisories
2. **Fix Release Process**
* Security patches released as priority updates
* Clear documentation of fixes
* Migration guides if needed
3. **Post-Fix Communication**
* Public security advisories
* Notification to affected users
* Updated security documentation
## Bug Bounty Program
Currently, we do not operate a formal bug bounty program. However, we do recognize and credit security researchers who:
* Follow responsible disclosure guidelines
* Provide clear and actionable reports
## Out of Scope
The following are typically out of scope:
* DOS/DDOS attacks
* Spam attacks
* Social engineering
* Physical security issues
* Issues requiring physical access
* Issues in dependencies (report to them directly)
* TLS configuration issues without practical impact
## Contact Information
* Security Issues: [security@better-auth.com](mailto:security@better-auth.com)
Remember: Security is a collaborative effort. Thank you for helping keep Better Auth and its users secure!
file: ./content/docs/contribute/testing.mdx
meta: {
"title": "Testing in Better Auth",
"description": "Testing Guide for Better Auth"
}
## Overview
We use Vitest as our testing framework of choice. Currently, we focus on unit tests, with plans to expand to integration testing in the future.
This guide will help you understand our testing philosophy and how to write effective tests.
We expect you to have knowledge of Vitest before you start writing tests. If
you're new to Vitest, we recommend you read the [Vitest
documentation](https://vitest.dev/guide/) first.
## Testing Philosophy
Our approach to testing emphasizes:
* Simplicity over complexity
* Real implementations over mocks
* Co-location of tests with source code
* Minimal test setup
* Clear and readable test cases
## Getting Started
### Setup
```bash
# Install dependencies including Vitest
pnpm install
# Run tests
pnpm run test
```
## Test Structure
### File Organization
We follow a co-location strategy for tests. Place your test files next to the source files they test:
### Test File Naming
* Use `.test.ts` extension for test files
* Name test files after the module they test
* For TypeScript, use `.test.ts` or `.test.tsx` for React components
## Writing Tests
### Basic Test Structure
This is just example code, and isn't meant to be used as-is. It's meant to
demonstrate the structure of a test file.
```ts title="auth.test.ts"
//...
import { describe, it, expect } from "vitest";
import { getTestInstance } from "./test-utils/test-instance";
describe("Authentication Module", () => {
it("should successfully authenticate a user", async () => {
const { client } = getTestInstance();
const result = await client.signIn.email({
email: "hello@gmail.com",
password: "123456",
});
expect(result.success).toBe(true);
expect(result.user).toBeDefined();
});
});
```
### Using `getTestInstance`
The test instance provides multiple handy properties that you can use to test your feature.
```ts
import { getTestInstance } from "../test-utils";
const {
client,
auth,
cookieSetter,
customFetchImpl,
db,
sessionSetter,
signInWithTestUser,
signInWithUser,
testUser,
} = getTestInstance();
```
Only create custom instances when needed
```typescript
import { getTestInstance } from "../test-utils";
const { auth } = getTestInstance({
customConfig: {
// your custom configuration
},
});
```
## Best Practices
### Do's
* Write descriptive test names that explain the expected behavior
* Test both success and failure cases
* Keep tests focused and atomic
* Use setup and teardown when needed
* Test edge cases and boundary conditions
This is just example code, and isn't meant to be used as-is. It's meant to
demonstrate the best Practices of testing.
```typescript
describe("User Authentication", () => {
it("should reject invalid credentials", async () => {
const { client } = getTestInstance();
const result = await client.signIn.email({
email: "invalid@example.com",
password: "wrong",
});
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
it("should handle empty inputs appropriately", async () => {
const { client } = getTestInstance();
const result = await client.signIn.email({
email: "",
password: "",
});
expect(result.success).toBe(false);
expect(result.error).toMatch(/required/);
});
});
```
### Don'ts
* Don't create unnecessary mocks
* Don't test implementation details
* Don't create separate test folders
* Don't write brittle tests
* Don't test external libraries
## Future Plans
* Implement integration testing
* Add end-to-end testing
* Expand API testing coverage
* Add performance testing
* Implement snapshot testing where appropriate
## Contributing
When submitting a PR:
* Ensure all tests pass
* Add tests for new features
* Update tests for modified features
* Follow existing test patterns
* Include meaningful assertions
Need help? Feel free to reach out in our Discord server!
file: ./content/docs/examples/astro.mdx
meta: {
"title": "Astro Example",
"description": "Better Auth Astro example."
}
This is an example of how to use Better Auth with Astro. It uses Solid for building the components.
**Implements the following features:**
Email & Password . Social Sign-in with Google . Passkeys . Email Verification . Password Reset . Two Factor Authentication . Profile Update . Session Management
## How to run
1. Clone the code sandbox (or the repo) and open it in your code editor
2. Provide .env file with the following variables
```txt
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
BETTER_AUTH_SECRET=
```
//if you don't have these, you can get them from the google developer console. If you don't want to use google sign-in, you can remove the google config from the `auth.ts` file.
3. Run the following commands
```bash
pnpm install
pnpm run dev
```
4. Open the browser and navigate to `http://localhost:3000`
file: ./content/docs/examples/next-js.mdx
meta: {
"title": "Next.js Example",
"description": "Better Auth Next.js example."
}
This is an example of how to use Better Auth with Next.
**Implements the following features:**
Email & Password . Social Sign-in . Passkeys . Email Verification . Password Reset . Two Factor Authentication . Profile Update . Session Management . Organization, Members and Roles
See [Demo](https://demo.better-auth.com)
## How to run
1. Clone the code sandbox (or the repo) and open it in your code editor
2. Move .env.example to .env and provide necessary variables
3. Run the following commands
```bash
pnpm install
pnpm dev
```
4. Open the browser and navigate to `http://localhost:3000`
file: ./content/docs/examples/nuxt.mdx
meta: {
"title": "Nuxt Example",
"description": "Better Auth Nuxt example."
}
This is an example of how to use Better Auth with Nuxt.
**Implements the following features:**
Email & Password . Social Sign-in with Google
## How to run
1. Clone the code sandbox (or the repo) and open it in your code editor
2. Move .env.example to .env and provide necessary variables
3. Run the following commands
```bash
pnpm install
pnpm dev
```
4. Open the browser and navigate to `http://localhost:3000`
file: ./content/docs/examples/remix.mdx
meta: {
"title": "Remix Example",
"description": "Better Auth Remix example."
}
This is an example of how to use Better Auth with Remix.
**Implements the following features:**
Email & Password . Social Sign-in with Google . Passkeys . Email Verification . Password Reset . Two Factor Authentication . Profile Update . Session Management
## How to run
1. Clone the code sandbox (or the repo) and open it in your code editor
2. Provide .env file with by copying the `.env.example` file and adding the variables
3. Run the following commands
```bash
pnpm install
pnpm run dev
```
4. Open the browser and navigate to `http://localhost:3000`
file: ./content/docs/examples/svelte-kit.mdx
meta: {
"title": "SvelteKit Example",
"description": "Better Auth SvelteKit example."
}
This is an example of how to use Better Auth with SvelteKit.
**Implements the following features:**
Email & Password . Social Sign-in with Google . Passkeys . Email Verification . Password Reset . Two Factor Authentication . Profile Update . Session Management
## How to run
1. Clone the code sandbox (or the repo) and open it in your code editor
2. Move .env.example to .env and provide necessary variables
3. Run the following commands
```bash
pnpm install
pnpm dev
```
4. Open the browser and navigate to `http://localhost:3000`
file: ./content/docs/guides/browser-extension-guide.mdx
meta: {
"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 BetterAuth 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 BetterAuth 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 BetterAuth 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 setup BetterAuth for your browser extension.
Add your desired UI and create your dream extension!
To learn more about the client BetterAuth 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"],
})
```
## 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 BetterAuth 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.
file: ./content/docs/guides/next-auth-migration-guide.mdx
meta: {
"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
Your existing user schema is likely compatible with Better Auth, so no changes are needed.
#### Session Schema
Map the following fields in the session schema:
* `expires` → `expiresAt`
* `sessionToken` → `token`
```typescript title="auth.ts"
export const auth = betterAuth({
// Other configs
session: {
fields: {
expiresAt: "expires", // e.g., "expires_at" or your existing field name
token: "sessionToken" // e.g., "session_token" or your existing field name
}
},
});
```
#### Accounts Schema
Map these fields in the accounts schema:
* `providerAccountId` → `accountId`
* `refresh_token` → `refreshToken`
* `access_token` → `accessToken`
* `access_token_expires` → `accessTokenExpiresAt`
* `id_token` → `idToken`
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
accounts: {
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())
expires DateTime @map("expiresAt") // Map `expires` to your existing field
token String @map("sessionToken") // Map `token` to your existing field
userId String
user User @relation(fields: [userId], references: [id])
}
```
### 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.
file: ./content/docs/guides/optimizing-for-performance.mdx
meta: {
"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` 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"
const auth = new 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 Solid Start, 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 Solid Start `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
Server-side rendering (SSR) is a powerful technique that allows you to render your application on the server and deliver fully rendered HTML to the client,
significantly enhancing performance, especially for data-intensive or complex applications.
To optimize SSR, minimize client-side data fetching by offloading data gathering to the server,
which reduces initial load times. Implement caching strategies for frequently accessed data to decrease server load and improve response times.
Additionally, simplify your rendering logic to ensure efficient server-side processing, consider using streaming to send parts of the rendered HTML as they become available,
and prioritize critical data for the initial render while loading less essential data asynchronously afterward.
## Database optimizations
Optimizing database performance is essential for any authentication library, including Better Auth.
Efficient database interactions can significantly enhance the speed and reliability of user authentication processes.
Since Better Auth supports a wide variety of databases, we cannot cover each one in detail regarding optimization techniques.
However, we can provide some general tips and strategies to improve your database performance.
Below are two critical techniques specifically tailored for Better Auth: using indexes and employing connection pools.
### Using indexes
Indexes are powerful tools that can dramatically improve the performance of database queries, especially in an authentication context where speed is crucial.
Here’s how to effectively utilize indexes in Better Auth:
1. **Types of Indexes**: There are several types of indexes, including:
* B-tree Indexes: The most common type, suitable for a wide range of queries.
* Hash Indexes: Useful for equality comparisons but not for range queries.
* Full-text Indexes: Designed for searching text within large text fields.
* Spatial Indexes: Optimized for spatial data types, such as geographic coordinates.
2. **When to Use Indexes**: Indexes are particularly beneficial for:
* Columns frequently used in WHERE clauses.
* Columns involved in JOIN operations.
* Columns used in ORDER BY and GROUP BY clauses.
3. **Trade-offs**: While indexes can significantly speed up read operations, they can also introduce overhead during write operations (INSERT, UPDATE, DELETE) because the index must be updated.
Therefore, it's essential to strike a balance between read and write performance.
4. **Monitoring and Maintenance**: Regularly monitor index usage and performance. Unused or rarely used indexes can be removed to reduce overhead. Additionally, consider rebuilding or reorganizing indexes periodically to maintain their efficiency.
You can read more about indexes in the database index wikipedia page.
#### Recommended fields to index
| Table | Fields | Plugin |
| ------------- | -------------------------- | ------------ |
| users | `email` | |
| accounts | `userId` | |
| sessions | `userId`, `token` | |
| verifications | `identifier` | |
| invitations | `userId`, `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.
### Using a connection pool
A connection pool is a critical component for optimizing database interactions in Better Auth, particularly in high-traffic scenarios where multiple authentication requests occur simultaneously.
Here are some benefits of using a connection pool and some configuration options to consider:
1. **Benefits of Connection Pools**:
* Reduced Latency: By reusing existing database connections, Better Auth can minimize the time spent establishing new connections, leading to faster authentication responses.
* Efficient Resource Management: Connection pools help manage database connections, preventing resource exhaustion and ensuring that the database can handle multiple authentication requests concurrently.
* Improved Scalability: Connection pools allow Better Auth to scale effectively, accommodating a growing number of users without a corresponding increase in database connection overhead.
2. **Configuration Options**:
* Maximum Pool Size: Set this based on expected traffic. A higher limit allows more simultaneous connections but may strain the database if set too high.
* Minimum Pool Size: Keep a baseline number of connections open to handle initial requests quickly.
* Connection Timeout: Define how long to wait for a connection to become available before timing out, ensuring that users don’t experience long delays.
* Idle Timeout: Specify how long a connection can remain idle before being closed, helping to manage resources effectively.
3. **Best Practices**:
Always close connections when they are no longer needed to return them to the pool, preventing connection leaks.
Monitor the performance of the connection pool and adjust configurations based on application load and usage patterns to optimize performance.
You can read more about connection pools in the connection pool wikipedia page.
file: ./content/docs/guides/your-first-plugin.mdx
meta: {
"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 create an 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]
reference: 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 setup 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!
file: ./content/docs/integrations/astro.mdx
meta: {
"title": "Astro Integration",
"description": "Integrate Better Auth with Astro."
}
Better Auth comes with first class support for Astro. This guide will show you how to integrate Better Auth with Astro.
Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation).
### Mount the handler
To enable Better Auth to handle requests, we need to mount the handler to a catch all API route. Create a file inside `/pages/api/auth` called `[...all].ts` and add the following code:
```ts title="pages/api/auth/[...all].ts"
import { auth } from "~/auth";
import type { APIRoute } from "astro";
export const ALL: APIRoute = async (ctx) => {
return auth.handler(ctx.request);
};
```
You can change the path on your better-auth configuration but it's recommended to keep it as `/api/auth/[...all]`
## Create a client
Astro supports multiple frontend frameworks, so you can easily import your client based on the framework you're using.
If you're not using a frontend framework, you can still import the vanilla client.
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/client"
export const authClient = createAuthClient()
```
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/react"
export const authClient = createAuthClient()
```
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/vue"
export const authClient = createAuthClient()
```
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/svelte"
export const authClient = createAuthClient()
```
```ts title="lib/auth-client.ts"
import { createAuthClient } from "better-auth/solid"
export const authClient = createAuthClient()
```
## Auth Middleware
### Astro Locals types
To have types for your Astro locals, you need to set it inside the `env.d.ts` file.
```ts title="env.d.ts"
///
declare namespace App {
// Note: 'import {} from ""' syntax does not work in .d.ts files.
interface Locals {
user: import("better-auth").User | null;
session: import("better-auth").Session | null;
}
}
```
### Middleware
To protect your routes, you can check if the user is authenticated using the `getSession` method in middleware and set the user and session data using the Astro locals with the types we set before. Start by creating a `middleware.ts` file in the root of your project and follow the example below:
```ts title="middleware.ts"
import { auth } from "@/auth";
import { defineMiddleware } from "astro:middleware";
export const onRequest = defineMiddleware(async (context, next) => {
const isAuthed = await auth.api
.getSession({
headers: context.request.headers,
})
if (isAuthed) {
context.locals.user = isAuthed.user;
context.locals.session = isAuthed.session;
} else {
context.locals.user = null;
context.locals.session = null;
}
return next();
});
```
### Getting session on the server inside `.astro` file
You can use `Astro.locals` to check if the user has session and get the user data from the server side. Here is an example of how you can get the session inside an `.astro` file:
```astro
---
import { UserCard } from "@/components/user-card";
const session = () => {
if (Astro.locals.session) {
return Astro.locals.session;
} else {
// Redirect to login page if the user is not authenticated
return Astro.redirect("/login");
}
}
---
```
file: ./content/docs/integrations/elysia.mdx
meta: {
"title": "Elysia Integration",
"description": "Integrate Better Auth with Elysia."
}
This integration guide is assuming you are using Elysia with bun server.
Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation).
### Mount the handler
We need to mount the handler to Elysia endpoint.
```ts
import { Elysia } from "elysia";
import { auth } from "./auth";
const app = new Elysia().mount(auth.handler).listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);
```
### CORS
To configure cors, you can use the `cors` plugin from `@elysiajs/cors`.
```ts
import { Elysia } from "elysia";
import { cors } from "@elysiajs/cors";
import { auth } from "./auth";
const app = new Elysia()
.use(
cors({
origin: "http://localhost:3001",
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
credentials: true,
allowedHeaders: ["Content-Type", "Authorization"],
}),
)
.mount(auth.handler)
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);
```
### Macro
You can use [macro](https://elysiajs.com/patterns/macro.html#macro) with [resolve](https://elysiajs.com/essential/handler.html#resolve) to provide session and user information before pass to view.
```ts
import { Elysia } from "elysia";
import { auth } from "./auth";
// user middleware (compute user and session and pass to routes)
const betterAuth = new Elysia({ name: "better-auth" })
.mount(auth.handler)
.macro({
auth: {
async resolve({ error, request: { headers } }) {
const session = await auth.api.getSession({
headers,
});
if (!session) return error(401);
return {
user: session.user,
session: session.session,
};
},
},
});
const app = new Elysia()
.use(betterAuth)
.get("/user", ({ user }) => user, {
auth: true,
})
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
);
```
This will allow you to access the `user` and `session` object in all of your routes.
file: ./content/docs/integrations/expo.mdx
meta: {
"title": "Expo Integration",
"description": "Integrate Better Auth with Expo."
}
Expo is a popular framework for building cross-platform apps with React Native. Better Auth supports both Expo native and web apps.
## Installation
## Configure A Better Auth Backend
Before using Better Auth with Expo, make sure you have a Better Auth backend set up. You can either use a separate server or leverage Expo's new [API Routes](https://docs.expo.dev/router/reference/api-routes) feature to host your Better Auth instance.
To get started, check out our [installation](/docs/installation) guide for setting up Better Auth on your server. If you prefer to check out the full example, you can find it [here](https://github.com/better-auth/better-auth/tree/main/examples/expo-example).
To use the new API routes feature in Expo to host your Better Auth instance you can create a new API route in your Expo app and mount the Better Auth handler.
```ts title="app/api/auth/[...auth]+api.ts"
import { auth } from "@/lib/auth"; // import Better Auth handler
const handler = auth.handler;
export { handler as GET, handler as POST }; // export handler for both GET and POST requests
```
## Install Better Auth and Expo Plugin
Install both the expo plugin in your server and both the Better Auth package and the Expo plugin in your Expo app.
```bash
npm install @better-auth/expo
```
```bash
pnpm add @better-auth/expo
```
```bash
yarn add @better-auth/expo
```
```bash
bun add @better-auth/expo
```
```bash
npm install better-auth
```
```bash
pnpm add better-auth
```
```bash
yarn add better-auth
```
```bash
bun add better-auth
```
## Add the Expo Plugin on Your Server
Add the Expo plugin to your Better Auth server.
```ts title="server.ts"
import { betterAuth } from "better-auth";
import { expo } from "@better-auth/expo";
export const auth = betterAuth({
plugins: [
expo()
]
});
```
## Initialize Better Auth Client
To initialize Better Auth in your Expo app, you need to call `createAuthClient` with the base url of your Better Auth backend. Make sure to import the client from `/react`.
You need to also import client plugin from `@better-auth/expo/client` and pass it to the `plugins` array when initializing the auth client.
This is important because:
* **Social Authentication Support:** enables social auth flows by handling authorization URLs and callbacks within the Expo web browser.
* **Secure Cookie Management:** stores cookies securely and automatically adds them to the headers of your auth requests.
```ts title="src/auth-client.ts"
import { createAuthClient } from "better-auth/react";
import { expoClient } from "@better-auth/expo/client";
import * as SecureStore from "expo-secure-store";
export const authClient = createAuthClient({
baseURL: "http://localhost:8081", /* base url of your Better Auth backend. */
plugins: [
expoClient({
scheme: "myapp",
storagePrefix: "myapp",
storage: SecureStore,
})
]
});
```
Be sure to include the full URL, including the path, if you've changed the default path from `/api/auth`.
## Scheme and Trusted Origins
Better Auth uses deep links to redirect users back to your app after authentication. To enable this, you need to add your app's scheme to the `trustedOrigins` list in your Better Auth config.
First, make sure you have a scheme defined in your `app.json` file.
```json title="app.json"
{
"expo": {
"scheme": "myapp"
}
}
```
Then, update your Better Auth config to include the scheme in the `trustedOrigins` list.
```ts title="auth.ts"
export const auth = betterAuth({
trustedOrigins: ["myapp://"]
})
```
## Configure Metro Bundler
To resolve better auth exports you'll need to enable `unstable_enablePackageExports` in your metro config.
```js title="metro.config.js"
const { getDefaultConfig } = require("expo/metro-config");
const config = getDefaultConfig(__dirname)
config.resolver.unstable_enablePackageExports = true; // [!code highlight]
module.exports = config;
```
If you can't enable `unstable_enablePackageExports` option, you can use [babel-plugin-module-resolver](https://github.com/tleunen/babel-plugin-module-resolver) to manually resolve the paths.
```ts title="babel.config.js"
module.exports = function (api) {
return {
plugins: [
[
"module-resolver",
{
alias: {
"better-auth/react": "path/to/node_modules/better-auth/dist/react.js"
"@better-auth/expo/client": "path/to/node_modules/@better-auth/expo/dist/client.js",
},
extensions: [".js", ".jsx", ".ts", ".tsx"],
},
],
],
}
}
```
Don't forget to clear the cache after making changes.
```bash
npx expo start --clear
```
## Usage
### Authenticating Users
With Better Auth initialized, you can now use the `authClient` to authenticate users in your Expo app.
```tsx title="app/sign-in.tsx"
import { useState } from "react";
import { View, TextInput, Button } from "react-native";
import { authClient } from "./auth-client";
export default function App() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const handleLogin = async () => {
await authClient.signIn.email({
email,
password,
})
};
return (
);
}
```
```tsx title="app/sign-up.tsx"
import { useState } from "react";
import { View, TextInput, Button } from "react-native";
import { authClient } from "./auth-client";
export default function App() {
const [email, setEmail] = useState("");
const [name, setName] = useState("");
const [password, setPassword] = useState("");
const handleLogin = async () => {
await authClient.signUp.email({
email,
password,
name
})
};
return (
);
}
```
For social sign-in, you can use the `authClient.signIn.social` method with the provider name and a callback URL.
```tsx title="app/social-sign-in.tsx"
import { Button } from "react-native";
export default function App() {
const handleLogin = async () => {
await authClient.signIn.social({
provider: "google",
callbackURL: "/dashboard" // this will be converted to a deep link (eg. `myapp://dashboard`) on native
})
};
return ;
}
```
### Session
Better Auth provides a `useSession` hook to access the current user's session in your app.
```tsx title="src/App.tsx"
import { authClient } from "@/lib/auth-client";
export default function App() {
const { data: session } = authClient.useSession();
return Welcome, {data.user.name};
}
```
On native, the session data will be cached in SecureStore. This will allow you to remove the need for a loading spinner when the app is reloaded. You can disable this behavior by passing the `disableCache` option to the client.
### Making Authenticated Requests to Your Server
To make authenticated requests to your server that require the user's session, you have two options:
1. Use the fetch client provided by Better Auth.
2. Retrieve the session cookie from `SecureStore` and manually add it to your request headers.
***
#### Option 1: Using the Fetch Client
Better Auth provides a built-in fetch client powered by [Better Fetch](http://better-fetch.vercel.app). This client automatically includes the session cookie in the headers of your requests.
```tsx
import { authClient } from "@/lib/auth-client";
const $fetch = authClient.$fetch;
// Example usage
$fetch("/api/secure-endpoint", {
method: "GET",
});
```
For more details, see the [Better Fetch documentation](http://better-fetch.vercel.app/docs).
#### Option 2: Adding the Cookie to Request Headers
If you prefer using your own fetch client, you can retrieve the session cookie stored in the device using `authClient.getCookie` and manually add it to your request headers.
```tsx
import { authClient } from "@/lib/auth-client";
const makeAuthenticatedRequest = async () => {
const cookies = authClient.getCookie(); // [!code highlight]
const headers = {
"Cookie": cookies, // [!code highlight]
};
const response = await fetch("http://localhost:8081/api/secure-endpoint", { headers });
const data = await response.json();
return data;
};
```
**Example: Usage With TRPC**
```tsx title="lib/trpc-provider.tsx"
//...other imports
import { authClient } from "@/lib/auth-client"; // [!code highlight]
export const api = createTRPCReact();
export function TRPCProvider(props: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient());
const [trpcClient] = useState(() =>
api.createClient({
links: [
httpBatchLink({
//...your other options
headers() {
const headers = new Map(); // [!code highlight]
const cookies = authClient.getCookie(); // [!code highlight]
if (cookies) { // [!code highlight]
headers.set("Cookie", cookies); // [!code highlight]
} // [!code highlight]
return Object.fromEntries(headers); // [!code highlight]
},
}),
],
}),
);
return (
{props.children}
);
}
```
## Options
### Expo Client
**storage**: the storage mechanism used to cache the session data and cookies.
```ts title="src/auth-client.ts"
import { createAuthClient } from "better-auth/react";
import SecureStorage from "expo-secure-store";
const authClient = createAuthClient({
baseURL: "http://localhost:8081",
storage: SecureStorage
});
```
**scheme**: scheme is used to deep link back to your app after a user has authenticated using oAuth providers. By default, Better Auth tries to read the scheme from the `app.json` file. If you need to override this, you can pass the scheme option to the client.
```ts title="src/auth-client.ts"
import { createAuthClient } from "better-auth/react";
const authClient = createAuthClient({
baseURL: "http://localhost:8081",
scheme: "myapp"
});
```
**disableCache**: By default, the client will cache the session data in SecureStore. You can disable this behavior by passing the `disableCache` option to the client.
```ts title="src/auth-client.ts"
import { createAuthClient } from "better-auth/react";
const authClient = createAuthClient({
baseURL: "http://localhost:8081",
disableCache: true
});
```
### Expo Servers
Server plugin options:
**overrideOrigin**: Override the origin for expo API routes (default: false). Enable this if you're facing cors origin issues with expo API routes.
file: ./content/docs/integrations/express.mdx
meta: {
"title": "Express Integration",
"description": "Integrate Better Auth with Express."
}
This guide will show you how to integrate Better Auth with [express.js](https://expressjs.com/).
Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation).
Note that CommonJS (cjs) isn't supported. Use ECMAScript Modules (ESM) by setting `"type": "module"` in your `package.json` or configuring your `tsconfig.json` to use ES modules.
### Mount the handler
To enable Better Auth to handle requests, we need to mount the handler to an API route. Create a catch-all route to manage all requests to `/api/auth/*` (or any other path specified in your Better Auth options).
Don’t use `express.json()` before the Better Auth handler. Use it only for other routes, or the client API will get stuck on "pending".
```ts title="server.ts"
import express from "express";
import { toNodeHandler } from "better-auth/node";
import { auth } from "./auth";
const app = express();
const port = 3005;
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(`Example app listening on port ${port}`);
});
```
After completing the setup, start your server. Better Auth will be ready to use. You can send a `GET` request to the `/ok` endpoint (`/api/auth/ok`) to verify that the server is running.
### Cors Configuration
To add CORS (Cross-Origin Resource Sharing) support to your Express server when integrating Better Auth, you can use the `cors` middleware. Below is an updated example showing how to configure CORS for your server:
```ts
import express from "express";
import cors from "cors"; // Import the CORS middleware
import { toNodeHandler, fromNodeHeaders } from "better-auth/node";
import { auth } from "./auth";
const app = express();
const port = 3005;
// Configure CORS middleware
app.use(
cors({
origin: "http://your-frontend-domain.com", // Replace with your frontend's origin
methods: ["GET", "POST", "PUT", "DELETE"], // Specify allowed HTTP methods
credentials: true, // Allow credentials (cookies, authorization headers, etc.)
})
);
```
### Getting the User Session
To retrieve the user's session, you can use the `getSession` method provided by the `auth` object. This method requires the request headers to be passed in a specific format. To simplify this process, Better Auth provides a `fromNodeHeaders` helper function that converts Node.js request headers to the format expected by Better Auth (a `Headers` object).
Here's an example of how to use `getSession` in an Express route:
```ts title="server.ts"
import { fromNodeHeaders } from "better-auth/node";
import { auth } from "./auth"; //your better auth instance
app.get("/api/me", async (req, res) => {
const session = await auth.api.getSession({
headers: fromNodeHeaders(req.headers),
});
return res.json(session);
});
```
file: ./content/docs/integrations/hono.mdx
meta: {
"title": "Hono Integration",
"description": "Integrate Better Auth with Hono."
}
Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation).
### Mount the handler
We need to mount the handler to Hono endpoint.
```ts
import { Hono } from "hono";
import { auth } from "./auth";
import { serve } from "@hono/node-server";
import { cors } from "hono/cors";
const app = new Hono();
app.on(["POST", "GET"], "/api/auth/*", (c) => {
return auth.handler(c.req.raw);
});
serve(app);
```
### Cors
To configure cors, you need to use the `cors` plugin from `hono/cors`.
```ts
import { Hono } from "hono";
import { auth } from "./auth";
import { serve } from "@hono/node-server";
import { cors } from "hono/cors";
const app = new Hono();
app.use(
"/api/auth/*", // or replace with "*" to enable cors for all routes
cors({
origin: "http://localhost:3001", // replace with your origin
allowHeaders: ["Content-Type", "Authorization"],
allowMethods: ["POST", "GET", "OPTIONS"],
exposeHeaders: ["Content-Length"],
maxAge: 600,
credentials: true,
}),
);
```
### Middleware
You can add a middleware to save the `session` and `user` in a `context` and also add validations for every route.
```ts
import { Hono } from "hono";
import { auth } from "./auth";
import { serve } from "@hono/node-server";
import { cors } from "hono/cors";
const app = new Hono<{
Variables: {
user: typeof auth.$Infer.Session.user | null;
session: typeof auth.$Infer.Session.session | null
}
}>();
app.use("*", async (c, next) => {
const session = await auth.api.getSession({ headers: c.req.raw.headers });
if (!session) {
c.set("user", null);
c.set("session", null);
return next();
}
c.set("user", session.user);
c.set("session", session.session);
return next();
});
app.on(["POST", "GET"], "/api/auth/*", (c) => {
return auth.handler(c.req.raw);
});
serve(app);
```
This will allow you to access the `user` and `session` object in all of your routes.
```ts
app.get("/session", async (c) => {
const session = c.get("session")
const user = c.get("user")
if(!user) return c.body(null, 401);
return c.json({
session,
user
});
});
```
### Cross-Domain Cookies
By default, all Better Auth cookies are set with `SameSite=Lax`. If you need to use cookies across different domains, you’ll need to set `SameSite=None` and `Secure=true`. However, we recommend using subdomains whenever possible, as this allows you to keep `SameSite=Lax`. To enable cross-subdomain cookies, simply turn on `crossSubDomainCookies` in your auth config.
```ts title="auth.ts"
export const auth = createAuth({
advanced: {
crossSubDomainCookies: {
enabled: true
}
}
})
```
If you still need to set `SameSite=None` and `Secure=true`, you can adjust these attributes globally through `cookieOptions` in the `createAuth` configuration.
```ts title="auth.ts"
export const auth = createAuth({
advanced: {
defaultCookieAttributes: {
sameSite: "none",
secure: true
}
}
})
```
You can also customize cookie attributes individually by setting them within `cookies` in your auth config.
```ts title="auth.ts"
export const auth = createAuth({
advanced: {
cookies: {
sessionToken: {
sameSite: "none",
secure: true
}
}
}
})
```
file: ./content/docs/integrations/next.mdx
meta: {
"title": "Next.js integration",
"description": "Integrate Better Auth with Next.js."
}
Better Auth can be easily integrated with Next.js. It'll also comes with utilities to make it easier to use Better Auth with Next.js.
Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation).
### Create API Route
We need to mount the handler to an API route. Create a route file inside `/api/auth/[...all]` directory. And add the following code:
```ts title="api/auth/[...all]/route.ts"
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { GET, POST } = toNextJsHandler(auth.handler);
```
You can change the path on your better-auth configuration but it's recommended to keep it as `/api/auth/[...all]`
For `pages` route, you need to use `toNodeHandler` instead of `toNextJsHandler` and set `bodyParser` to `false` in the `config` object. Here is an example:
```ts title="pages/api/auth/[...all].ts"
import { toNodeHandler } from "better-auth/node"
import { auth } from "@/lib/auth"
// Disallow body parsing, we will parse it manually
export const config = { api: { bodyParser: false } }
export default toNodeHandler(auth.handler)
```
## Create a client
Create a client instance. You can name the file anything you want. Here we are creating `client.ts` file inside the `lib/` directory.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/react" // make sure to import from better-auth/react
export const authClient = createAuthClient({
//you can pass client configuration here
})
```
Once you have created the client, you can use it to sign up, sign in, and perform other actions.
Some of the actions are reactive. The client use [nano-store](https://github.com/nanostores/nanostores) to store the state and re-render the components when the state changes.
The client also uses [better-fetch](https://github.com/bekacru/better-fetch) to make the requests. You can pass the fetch configuration to the client.
## RSC and Server actions
The `api` object exported from the auth instance contains all the actions that you can perform on the server. Every endpoint made inside Better Auth is a invocable as a function. Including plugins endpoints.
**Example: Getting Session on a server action**
```tsx title="server.ts"
import { auth } from "@/lib/auth"
import { headers } from "next/headers"
const someAuthenticatedAction = async () => {
"use server";
const session = await auth.api.getSession({
headers: await headers()
})
};
```
**Example: Getting Session on a RSC**
```tsx
import { auth } from "@/lib/auth"
import { headers } from "next/headers"
export async function ServerComponent() {
const session = await auth.api.getSession({
headers: await headers()
})
if(!session) {
return
Not authenticated
}
return (
Welcome {session.user.name}
)
}
```
### Server Action Cookies
When you call a function that needs to set cookies, like `signInEmail` or `signUpEmail` in a server action, cookies won’t be set. This is because server actions need to use the `cookies` helper from Next.js to set cookies.
To simplify this, you can use the `nextCookies` plugin, which will automatically set cookies for you whenever a `Set-Cookie` header is present in the response.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { nextCookies } from "better-auth/next-js";
export const auth = betterAuth({
//...your config
plugins: [nextCookies()] // make sure this is the last plugin in the array // [!code highlight]
})
```
Now, when you call functions that set cookies, they will be automatically set.
```ts
"use server";
import { auth } from "@/lib/auth"
const signIn = async () => {
await auth.api.signInEmail({
body: {
email: "user@email.com",
password: "password",
}
})
}
```
## Middleware
In Next.js middleware, it's recommended to only check for the existence of a session cookie to handle redirection. To avoid blocking requests by making API or database calls.
You can use the `getSessionCookie` helper from Better Auth for this purpose:
```ts
import { NextRequest, NextResponse } from "next/server";
import { getSessionCookie } from "better-auth";
export async function middleware(request: NextRequest) {
const sessionCookie = getSessionCookie(request); // Optionally pass config as the second argument if cookie name or prefix is customized.
if (!sessionCookie) {
return NextResponse.redirect(new URL("/", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard"], // Specify the routes the middleware applies to
};
```
If you need the full session object, you'll have to fetch it from the `/get-session` API route. Since Next.js middleware doesn't support running Node.js APIs directly, you must make an HTTP request.
The example uses [better-fetch](https://better-fetch.vercel.app), but you can use any fetch library.
```ts
import { betterFetch } from "@better-fetch/fetch";
import type { auth } from "@/lib/auth";
import { NextRequest, NextResponse } from "next/server";
type Session = typeof auth.$Infer.Session;
export async function middleware(request: NextRequest) {
const { data: session } = await betterFetch("/api/auth/get-session", {
baseURL: request.nextUrl.origin,
headers: {
cookie: request.headers.get("cookie") || "", // Forward the cookies from the request
},
});
if (!session) {
return NextResponse.redirect(new URL("/sign-in", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard"], // Apply middleware to specific routes
};
```
file: ./content/docs/integrations/nitro.mdx
meta: {
"title": "Nitro Integration",
"description": "Integrate Better Auth with Nitro."
}
Better Auth can be integrated with your [Nitro Application](https://nitro.build/)(an open source framework to build web servers).
This guide aims to help you integrate Better Auth with your Nitro application in a few simple steps.
## Create a new Nitro Application
Start by scaffolding a new Nitro application using the following command:
```bash title="Terminal"
npx giget@latest nitro nitro-app --install
```
This will create the `nitro-app` directory and install all the dependencies. You can now open the `nitro-app` directory in your code editor.
### Prisma Adapter Setup
This guide assumes that you have a basic understanding of Prisma. If you are new to Prisma, you can check out the [Prisma documentation](https://www.prisma.io/docs/getting-started).
The `sqlite` database used in this guide will not work in a production environment. You should replace it with a production-ready database like `PostgreSQL`.
For this guide, we will be using the Prisma adapter. You can install prisma client by running the following command:
```bash
npm install @prisma/client
```
```bash
pnpm add @prisma/client
```
```bash
yarn add @prisma/client
```
```bash
bun add @prisma/client
```
`prisma` can be installed as a dev dependency using the following command:
```bash
npm install -D prisma
```
```bash
pnpm add -D prisma
```
```bash
yarn add --dev prisma
```
```bash
bun add --dev prisma
```
Generate a `schema.prisma` file in the `prisma` directory by running the following command:
```bash title="Terminal"
npx prisma init
```
You can now replace the contents of the `schema.prisma` file with the following:
```prisma title="prisma/schema.prisma"
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
// Will be deleted. Just need it to generate the prisma client
model Test {
id Int @id @default(autoincrement())
name String
}
```
Ensure that you update the `DATABASE_URL` in your `.env` file to point to the location of your database.
```env title=".env"
DATABASE_URL="file:./dev.db"
```
Run the following command to generate the Prisma client & sync the database:
```bash title="Terminal"
npx prisma db push
```
### Install & Configure Better Auth
Follow steps 1 & 2 from the [installation guide](/docs/installation) to install Better Auth in your Nitro application & set up the environment variables.
Once that is done, create your better auth instance within the `server/utils/auth.ts` file.
```ts title="server/utils/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" }),
emailAndPassword: { enabled: true },
});
```
### Update Prisma Schema
Use the Better Auth CLI to update your Prisma schema with the required models by running the following command:
```bash title="Terminal"
npx @better-auth/cli generate --config server/utils/auth.ts
```
The `--config` flag is used to specify the path to the file where you have created your Better Auth instance.
Head over to the `prisma/schema.prisma` file & save the file to trigger the format on save.
After saving the file, you can run the `npx prisma db push` command to update the database schema.
## Mount The Handler
You can now mount the Better Auth handler in your Nitro application. You can do this by adding the following code to your `server/routes/api/auth/[...all].ts` file:
```ts title="server/routes/api/auth/[...all].ts"
export default defineEventHandler((event) => {
return auth.handler(toWebRequest(event));
});
```
This is a [catch-all](https://nitro.build/guide/routing#catch-all-route) route that will handle all requests to `/api/auth/*`.
### Cors
You can configure CORS for your Nitro app by creating a plugin.
Start by installing the cors package:
```bash
npm install cors
```
```bash
pnpm add cors
```
```bash
yarn add cors
```
```bash
bun add cors
```
You can now create a new file `server/plugins/cors.ts` and add the following code:
```ts title="server/plugins/cors.ts"
import cors from "cors";
export default defineNitroPlugin((plugin) => {
plugin.h3App.use(
fromNodeMiddleware(
cors({
origin: "*",
}),
),
);
});
```
This will enable CORS for all routes. You can customize the `origin` property to allow requests from specific domains. Ensure that the config is in sync with your frontend application.
### Auth Guard/Middleware
You can add an auth guard to your Nitro application to protect routes that require authentication. You can do this by creating a new file `server/utils/require-auth.ts` and adding the following code:
```ts title="server/utils/require-auth.ts"
import { EventHandler, H3Event } from "h3";
import { fromNodeHeaders } from "better-auth/node";
/**
* Middleware used to require authentication for a route.
*
* Can be extended to check for specific roles or permissions.
*/
export const requireAuth: EventHandler = async (event: H3Event) => {
const headers = event.headers;
const session = await auth.api.getSession({
headers: headers,
});
if (!session)
throw createError({
statusCode: 401,
statusMessage: "Unauthorized",
});
// You can save the session to the event context for later use
event.context.auth = session;
};
```
You can now use this event handler/middleware in your routes to protect them:
```ts title="server/routes/api/secret.get.ts"
// Object syntax of the route handler
export default defineEventHandler({
// The user has to be logged in to access this route
onRequest: [requireAuth],
handler: async (event) => {
setResponseStatus(event, 201, "Secret data");
return { message: "Secret data" };
},
});
```
### Example
You can find an example of a Nitro application integrated with Better Auth & Prisma [here](https://github.com/BayBreezy/nitrojs-better-auth-prisma).
file: ./content/docs/integrations/nuxt.mdx
meta: {
"title": "Nuxt Integration",
"description": "Integrate Better Auth with Nuxt."
}
Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation).
### Create API Route
We need to mount the handler to an API route. Create a file inside `/server/api` called `[...auth].ts` and add the following code:
```ts title="server/api/[...auth].ts"
import { auth } from "~/utils/auth"; // import your auth config
export default defineEventHandler((event) => {
return auth.handler(toWebRequest(event));
});
```
You can change the path on your better-auth configuration but it's recommended to keep it as `/api/[...auth]`
### Migrate the database
Run the following command to create the necessary tables in your database:
```bash
npx @better-auth/cli migrate
```
## Create a client
Create a client instance. You can name the file anything you want. Here we are creating `client.ts` file inside the `lib/` directory.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/vue" // make sure to import from better-auth/vue
export const authClient = createAuthClient({
//you can pass client configuration here
})
```
Once you have created the client, you can use it to sign up, sign in, and perform other actions.
Some of the actions are reactive.
### Example usage
```vue title="index.vue"
{{ session.data }}
```
### Server Usage
The `api` object exported from the auth instance contains all the actions that you can perform on the server. Every endpoint made inside Better Auth is a invocable as a function. Including plugins endpoints.
**Example: Getting Session on a server API route**
```tsx title="server/api/example.ts"
import { auth } from "~/lib/auth";
export default defineEventHandler((event) => {
const session = await auth.api.getSession({
headers: event.headers
});
if(session) {
// access the session.session && session.user
}
});
```
### SSR Usage
If you are using Nuxt with SSR, you can use the `useSession` function in the `setup` function of your page component and pass `useFetch` to make it work with SSR.
```vue title="index.vue"
{{ session }}
```
### Middleware
To add middleware to your Nuxt project, you can use the `useSession` method from the client.
```ts title="middleware/auth.global.ts"
import { authClient } from "~/lib/auth-client";
export default defineNuxtRouteMiddleware(async (to, from) => {
const { data: session } = await authClient.useSession(useFetch);
if (!session.value) {
if (to.path === "/dashboard") {
return navigateTo("/");
}
}
});
```
### Resources & Examples
* [Nuxt and Nuxt Hub example](https://github.com/atinux/nuxthub-better-auth) on GitHub.
* [NuxtZzle is Nuxt,Drizzle ORM example](https://github.com/leamsigc/nuxt-better-auth-drizzle) on github [preview](https://nuxt-better-auth.giessen.dev/)
* [Nuxt example](https://stackblitz.com/github/better-auth/better-auth/tree/main/examples/nuxt-example) on StackBlitz.
file: ./content/docs/integrations/remix.mdx
meta: {
"title": "Remix Integration",
"description": "Integrate Better Auth with Remix."
}
Better Auth can be easily integrated with Remix. This guide will show you how to integrate Better Auth with Remix.
You can follow the steps from [installation](/docs/installation) to get started or you can follow this guide to make it the Remix-way.
If you have followed the installation steps, you can skip the first step.
## Create auth instance
Create a file named `auth.server.ts` in one of these locations:
* Project root
* `lib/` folder
* `utils/` folder
You can also nest any of these folders under `app/` folder. (e.g. `app/lib/auth.server.ts`)
And in this file, import Better Auth and create your instance.
Make sure to export the auth instance with the variable name `auth` or as a `default` export.
```ts title="app/lib/auth.server.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
database: {
provider: "postgres", //change this to your database provider
url: process.env.DATABASE_URL, // path to your database or connection string
}
})
```
## Create API Route
We need to mount the handler to a API route. Create a resource route file `api.auth.$.ts` inside `app/routes/` directory. And add the following code:
```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)
}
```
You can change the path on your better-auth configuration but it's recommended to keep it as `routes/api.auth.$.ts`
## Create a client
Create a client instance. Here we are creating `auth.client.ts` file inside the `lib/` directory.
```ts title="app/lib/auth.client.ts"
import { createAuthClient } from "better-auth/react" // make sure to import from better-auth/react
export const authClient = createAuthClient({
//you can pass client configuration here
})
```
Once you have created the client, you can use it to sign up, sign in, and perform other actions.
### Example usage
#### Sign Up
```ts title="app/routes/signup.tsx"
import { Form } from "@remix-run/react"
import { useState } from "react"
import { authClient } from "~/lib/auth.client"
export default function SignUp() {
const [email, setEmail] = useState("")
const [name, setName] = useState("")
const [password, setPassword] = useState("")
const signUp = async () => {
await authClient.signUp.email(
{
email,
password,
name,
},
{
onRequest: (ctx) => {
// show loading state
},
onSuccess: (ctx) => {
// redirect to home
},
onError: (ctx) => {
alert(ctx.error)
},
},
)
}
return (
Sign Up
)
}
```
#### Sign In
```ts title="app/routes/signin.tsx"
import { Form } from "@remix-run/react"
import { useState } from "react"
import { authClient } from "~/services/auth.client"
export default function SignIn() {
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")
const signIn = async () => {
await authClient.signIn.email(
{
email,
password,
},
{
onRequest: (ctx) => {
// show loading state
},
onSuccess: (ctx) => {
// redirect to home
},
onError: (ctx) => {
alert(ctx.error)
},
},
)
}
return (
Sign In
)
}
```
file: ./content/docs/integrations/solid-start.mdx
meta: {
"title": "SolidStart Integration",
"description": "Integrate Better Auth with SolidStart."
}
Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation).
### Mount the handler
We need to mount the handler to SolidStart server. Put the following code in your `*auth.ts` file inside `/routes/api/auth` folder.
```ts title="*auth.ts"
import { auth } from "~/lib/auth";
import { toSolidStartHandler } from "better-auth/solid-start";
export const { GET, POST } = toSolidStartHandler(auth);
```
file: ./content/docs/integrations/svelte-kit.mdx
meta: {
"title": "SvelteKit Integration",
"description": "Integrate Better Auth with SvelteKit."
}
Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation).
### Mount the handler
We need to mount the handler to SvelteKit server hook.
```ts title="hooks.server.ts"
import { auth } from "$lib/auth";
import { svelteKitHandler } from "better-auth/svelte-kit";
export async function handle({ event, resolve }) {
return svelteKitHandler({ event, resolve, auth });
}
```
## Create a client
Create a client instance. You can name the file anything you want. Here we are creating `client.ts` file inside the `lib/` directory.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/svelte" // make sure to import from better-auth/svelte
export const authClient = createAuthClient({
// you can pass client configuration here
})
```
Once you have created the client, you can use it to sign up, sign in, and perform other actions.
Some of the actions are reactive. The client use [nano-store](https://github.com/nanostores/nanostores) to store the state and reflect changes when there is a change like a user signing in or out affecting the session state.
### Example usage
```svelte
{#if $session.data}
{$session?.data?.user.name}
{:else}
{/if}
```
### Example: Getting User session and role in a layout
```ts title="+layout.server.ts"
import type { LayoutServerLoad } from './$types';
import { error, redirect } from '@sveltejs/kit';
import { auth } from '$lib/auth';
export const load: LayoutServerLoad = async ({ request }) => {
const session = await auth.api.getSession({
headers: request.headers
});
if (!session) {
console.log('Admin : No session');
throw redirect(302, '/login');
}
if (session.user.role !== 'admin') {
console.log('Admin : Not admin');
throw error(403, 'Forbidden');
}
};
```
### Example: Storing session on locals and protecting a route with layout
We first get the session and set it to event.locals
```ts title="hooks.server.ts"
import type { Handle } from '@sveltejs/kit';
import { auth } from '$lib/auth-client';
export const handle: Handle = async ({ event, resolve }) => {
// Get the session
const session = await auth.api.getSession({
headers: event.request.headers
});
// Set session and user to locals
event.locals.session = session?.data?.session;
event.locals.user = session?.data?.user;
const response = await resolve(event);
return response;
};
```
Then on the +layout.server.ts of the protected routes
```ts title="+layout.server.ts"
import type { LayoutServerLoad } from './$types';
import { error, redirect } from '@sveltejs/kit';
export const load: LayoutServerLoad = async ({ locals }) => {
const session = locals.session;
const user = locals.user;
if (!session) {
console.log('Admin : No session');
throw redirect(302, '/login');
}
if (user?.role !== 'admin') {
console.log('Admin : Not admin');
throw error(403, 'Forbidden');
}
};
```
if you do this , make sure to also set the types on your app.d.ts
```ts title="app.d.ts"
// See https://kit.svelte.dev/docs/types#app
// for information about these interfaces
declare global {
namespace App {
interface Locals {
session: Session | undefined;
user: User | undefined;
}
// interface Error {}
// interface Locals {}
// interface PageData {}
// interface Platform {}
}
}
export { };
```
file: ./content/docs/integrations/tanstack.mdx
meta: {
"title": "TanStack Start Integration",
"description": "Integrate Better Auth with TanStack Start."
}
This integration guide is assuming you are using TanStack Start.
Before you start, make sure you have a Better Auth instance configured. If you haven't done that yet, check out the [installation](/docs/installation).
### Mount the handler
We need to mount the handler to a TanStack API endpoint.
Create a new file: `/app/routes/api/auth/$.ts`
```ts
import { auth } from '@/lib/auth'
import { createAPIFileRoute } from '@tanstack/start/api'
export const APIRoute = createAPIFileRoute('/api/auth/$')({
GET: ({ request }) => {
return auth.handler(request)
},
POST: ({ request }) => {
return auth.handler(request)
},
})
```
This will allow you to access use the `getSession` method in all of your routes.
file: ./content/docs/plugins/2fa.mdx
meta: {
"title": "Two-Factor Authentication (2FA)",
"description": "Enhance your app's security with two-factor authentication."
}
`OTP` `TOTP` `Backup Codes` `Trusted Devices`
Two-Factor Authentication (2FA) adds an extra security step when users log in. Instead of just using a password, they'll need to provide a second form of verification. This makes it much harder for unauthorized people to access accounts, even if they've somehow gotten the password.
This plugin offers two main methods to do a second factor verification:
1. **OTP (One-Time Password)**: A temporary code sent to the user's email or phone.
2. **TOTP (Time-based One-Time Password)**: A code generated by an app on the user's device.
**Additional features include:**
* Generating backup codes for account recovery
* Enabling/disabling 2FA
* Managing trusted devices
## Installation
### Add the plugin to your auth config
Add the two-factor plugin to your auth configuration and specify your app name as the issuer.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins" // [!code highlight]
export const auth = betterAuth({
// ... other config options
appName: "My App", // provide your app name. It'll be used as an issuer. // [!code highlight]
plugins: [
twoFactor() // [!code highlight]
]
})
```
### Migrate the database
Run the migration or generate the schema to add the necessary fields and tables to the database.
```bash
npx @better-auth/cli migrate
```
```bash
npx @better-auth/cli generate
```
See the [Schema](#schema) section to add the fields manually.
### Add the client plugin
Add the client plugin and Specify where the user should be redirected if they need to verify 2nd factor
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
import { twoFactorClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [
twoFactorClient()
]
})
```
## Usage
### Enabling 2FA
To enable two-factor authentication, call `twoFactor.enable` with the user's password:
```ts title="two-factor.ts"
const { data } = await authClient.twoFactor.enable({
password: "password" // user password required
})
```
When 2FA is enabled:
* An encrypted `secret` and `backupCodes` are generated.
* `enable` returns `totpURI` and `backupCodes`.
Note: `twoFactorEnabled` won’t be set to `true` until the user verifies their TOTP code.
To verify, display the QR code for the user to scan with their authenticator app. After they enter the code, call `verifyTotp`:
```ts
await authClient.twoFactor.verifyTotp({
code: "" // user input
})
```
You can skip verification by setting `skipVerificationOnEnable` to true in your plugin config.
### Sign In with 2FA
When a user with 2FA enabled tries to sign in via email, the response will contain `twoFactorRedirect` set to `true`. This indicates that the user needs to verify their 2FA code.
```ts title="sign-in.ts"
await authClient.signIn.email({
email: "user@example.com",
password: "password123",
})
```
You can handle this in the `onSuccess` callback or by providing a `onTwoFactorRedirect` callback in the plugin config.
```ts title="sign-in.ts"
import { createAuthClient } from "better-auth/client";
import { twoFactorClient } from "better-auth/client/plugins";
const authClient = createAuthClient({
plugins: [twoFactorClient({
onTwoFactorRedirect(){
// Handle the 2FA verification globally
}
})]
})
```
Or you can handle it in place:
```ts
await authClient.signIn.email({
email: "user@example.com",
password: "password123",
}, {
async onSuccess(context) {
if (context.data.twoFactorRedirect) {
// Handle the 2FA verification in place
}
}
}
})
```
#### Using `auth.api`
When you call `auth.api.signInEmail` on the server, and the user has 2FA enabled, it will, by default, respond with an object where `twoFactorRedirect` is set to `true`. This behavior isn’t inferred in TypeScript, which can be misleading. We recommend passing `asResponse: true` to receive the Response object instead.
```ts
const response = await auth.api.signInEmail({
email: "my-email@email.com",
password: "secure-password",
asResponse: true
})
```
### TOTP
TOTP (Time-Based One-Time Password) is an algorithm that generates a unique password for each login attempt using time as a counter. Every fixed interval (Better Auth defaults to 30 seconds), a new password is generated. This addresses several issues with traditional passwords: they can be forgotten, stolen, or guessed. OTPs solve some of these problems, but their delivery via SMS or email can be unreliable (or even risky, considering it opens new attack vectors).
TOTP, however, generates codes offline, making it both secure and convenient. You just need an authenticator app on your phone, and you’re set—no internet required.
#### Getting TOTP URI
After enabling 2FA, you can get the TOTP URI to display to the user. This URI is generated by the server using the `secret` and `issuer` and can be used to generate a QR code for the user to scan with their authenticator app.
```ts
const { data, error } = await authClient.twoFactor.getTotpUri({
password: "password" // user password required
})
```
**Example: Using React**
```tsx title="user-card.tsx"
import QRCode from "react-qr-code";
export default function UserCard(){
const { data: session } = client.useSession();
const { data: qr } = useQuery({
queryKey: ["two-factor-qr"],
queryFn: async () => {
const res = await authClient.twoFactor.getTotpUri();
return res.data;
},
enabled: !!session?.user.twoFactorEnabled,
});
return (
)
}
```
By default the issuer for TOTP is set to the app name provided in the auth config or if not provided it will be set to `Better Auth`. You can override this by passing `issuer` to the plugin config.
#### Verifying TOTP
After the user has entered their 2FA code, you can verify it using `twoFactor.verifyTotp` method.
```ts
const verifyTotp = async (code: string) => {
const { data, error } = await authClient.twoFactor.verifyTotp({ code })
}
```
### OTP
OTP (One-Time Password) is similar to TOTP but a random code is generated and sent to the user's email or phone.
Before using OTP to verify the second factor, you need to configure `sendOTP` in your Better Auth instance. This function is responsible for sending the OTP to the user's email, phone, or any other method supported by your application.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { twoFactor } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [
twoFactor({
otpOptions: {
async sendOTP({ user, otp }, request) {
// send otp to user
},
},
})
]
})
```
#### Sending OTP
Sending an OTP is done by calling the `twoFactor.sendOtp` function. This function will trigger your sendOTP implementation that you provided in the Better Auth configuration.
```ts
const { data, error } = await authClient.twoFactor.sendOtp()
if (data) {
// redirect or show the user to enter the code
}
```
#### Verifying OTP
After the user has entered their OTP code, you can verify it
```ts
const verifyOtp = async (code: string) => {
await authClient.twoFactor.verifyOtp({ code }, {
onSuccess(){
//redirect the user on success
},
onError(ctx){
alert(ctx.error.message)
}
})
}
```
### Backup Codes
Backup codes are generated and stored in the database. This can be used to recover access to the account if the user loses access to their phone or email.
#### Generating Backup Codes
Generate backup codes for account recovery:
```ts
const { data, error } = await authClient.twoFactor.generateBackupCodes({
password: "password" // user password required
})
if (data) {
// Show the backup codes to the user
}
```
#### Using Backup Codes
You can now allow users to provider backup code as account recover method.
```ts
await authClient.twoFactor.verifyBackupCode({code: ""}, {
onSuccess(){
//redirect the user on success
},
onError(ctx){
alert(ctx.error.message)
}
})
```
once a backup code is used, it will be removed from the database and can't be used again.
#### Viewing Backup Codes
You can view the backup codes at any time by calling `viewBackupCodes`. This action can only be performed on the server using `auth.api`.
```ts
await auth.api.viewBackupCodes({
body: {
userId: "user-id"
}
})
```
### Trusted Devices
You can mark a device as trusted by passing `trustDevice` to `verifyTotp` or `verifyOtp`.
```ts
const verify2FA = async (code: string) => {
const { data, error } = await authClient.twoFactor.verifyTotp({
code,
callbackURL: "/dashboard",
trustDevice: true // Mark this device as trusted
})
if (data) {
// 2FA verified and device trusted
}
}
```
When `trustDevice` is set to `true`, the current device will be remembered for 60 days. During this period, the user won't be prompted for 2FA on subsequent sign-ins from this device. The trust period is refreshed each time the user signs in successfully.
### Issuer
By adding an `issuer` you can set your application name for the 2fa application.
For example, if your user uses Google Auth, the default appName will show up as `Better Auth`. However, by using the following code, it will show up as `my-app-name`.
```ts
twoFactor({
issuer: "my-app-name" // [!code highlight]
})
```
***
## Schema
The plugin requires 1 additional fields in the `user` table and 1 additional table to store the two factor authentication data.
Table: `twoFactor`
## Options
### Server
**twoFactorTable**: The name of the table that stores the two factor authentication data. Default: `twoFactor`.
**skipVerificationOnEnable**: Skip the verification process before enabling two factor for a user.
**Issuer**: The issuer is the name of your application. It's used to generate TOTP codes. It'll be displayed in the authenticator apps.
**TOTP options**
these are options for TOTP.
**OTP options**
these are options for OTP.
**Backup Code Options**
backup codes are generated and stored in the database when the user enabled two factor authentication. This can be used to recover access to the account if the user loses access to their phone or email.
### Client
To use the two factor plugin in the client, you need to add it on your plugins list.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
import { twoFactorClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [
twoFactorClient({ // [!code highlight]
onTwoFactorRedirect(){
window.location.href = "/2fa" // Handle the 2FA verification redirect
}
}) // [!code highlight]
]
})
```
**Options**
`onTwoFactorRedirect`: A callback that will be called when the user needs to verify their 2FA code. This can be used to redirect the user to the 2FA page.
file: ./content/docs/plugins/admin.mdx
meta: {
"title": "Admin",
"description": "Admin plugin for Better Auth"
}
The Admin plugin provides a set of administrative functions for user management in your application. It allows administrators to perform various operations such as creating users, managing user roles, banning/unbanning users, impersonating users, and more.
## Installation
### Add the plugin to your auth config
To use the Admin plugin, add it to your auth config.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { admin } from "better-auth/plugins" // [!code highlight]
export const auth = betterAuth({
// ... other config options
plugins: [
admin() // [!code highlight]
]
})
```
### Migrate the database
Run the migration or generate the schema to add the necessary fields and tables to the database.
```bash
npx @better-auth/cli migrate
```
```bash
npx @better-auth/cli generate
```
See the [Schema](#schema) section to add the fields manually.
### Add the client plugin
Next, include the admin client plugin in your authentication client instance.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
import { adminClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [
adminClient()
]
})
```
## Usage
Before performing any admin operations, the user must be authenticated with an admin account. An admin is any user assigned the `admin` role. For the first admin user, you'll need to manually assign the `admin` role to their account in your database.
### Create User
Allows an admin to create a new user.
```ts title="admin.ts"
const newUser = await authClient.admin.createUser({
name: "Test User",
email: "test@example.com",
password: "password123",
role: "user",
data: {
// any additional on the user table including plugin fields and custom fields
customField: "customValue"
}
});
```
### List Users
Allows an admin to list all users in the database.
```ts title="admin.ts"
const users = await authClient.admin.listUsers({
query: {
limit: 10,
}
});
```
By default, 100 users are returned. You can adjust the limit and offset using the following query parameters:
* `search`: The search query to apply to the users. It can be an object with the following properties:
* `field`: The field to search on, which can be `email` or `name`.
* `operator`: The operator to use for the search. It can be `contains`, `starts_with`, or `ends_with`.
* `value`: The value to search for.
* `limit`: The number of users to return.
* `offset`: The number of users to skip.
* `sortBy`: The field to sort the users by.
* `sortDirection`: The direction to sort the users by. Defaults to `asc`.
* `filter`: The filter to apply to the users. It can be an array of objects.
```ts title="admin.ts"
const users = await authClient.admin.listUsers({
query: {
searchField: "email",
searchOperator: "contains",
searchValue: "@example.com",
limit: 10,
offset: 0,
sortBy: "createdAt",
sortDirection: "desc"
filterField: "role",
filterOperator: "eq",
filterValue: "admin"
}
});
```
### Set User Role
Changes the role of a user.
```ts title="admin.ts"
const updatedUser = await authClient.admin.setRole({
userId: "user_id_here",
role: "admin"
});
```
### Ban User
Bans a user, preventing them from signing in and revokes all of their existing sessions.
```ts title="admin.ts"
const bannedUser = await authClient.admin.banUser({
userId: "user_id_here",
banReason: "Spamming", // Optional (if not provided, the default ban reason will be used - No reason)
banExpiresIn: 60 * 60 * 24 * 7 // Optional (if not provided, the ban will never expire)
});
```
### Unban User
Removes the ban from a user, allowing them to sign in again.
```ts title="admin.ts"
const unbannedUser = await authClient.admin.unbanUser({
userId: "user_id_here"
});
```
### List User Sessions
Lists all sessions for a user.
```ts title="admin.ts"
const sessions = await authClient.admin.listUserSessions({
userId: "user_id_here"
});
```
### Revoke User Session
Revokes a specific session for a user.
```ts title="admin.ts"
const revokedSession = await authClient.admin.revokeUserSession({
sessionToken: "session_token_here"
});
```
### Revoke All Sessions for a User
Revokes all sessions for a user.
```ts title="admin.ts"
const revokedSessions = await authClient.admin.revokeUserSessions({
userId: "user_id_here"
});
```
### Impersonate User
This feature allows an admin to create a session that mimics the specified user. The session will remain active until either the browser session ends or it reaches 1 hour. You can change this duration by setting the `impersonationSessionDuration` option.
```ts title="admin.ts"
const impersonatedSession = await authClient.admin.impersonateUser({
userId: "user_id_here"
});
```
### Stop Impersonating User
To stop impersonating a user and continue with the admin account, you can use `stopImpersonating`
```ts title="admin.ts"
await authClient.admin.stopImpersonating();
```
### Remove User
Hard deletes a user from the database.
```ts title="admin.ts"
const deletedUser = await authClient.admin.removeUser({
userId: "user_id_here"
});
```
## Schema
This plugin adds the following fields to the `user` table:
And adds one field in the `session` table:
## Options
### Default Role
The default role for a user. Defaults to `user`.
```ts title="auth.ts"
admin({
defaultRole: "regular"
})
```
you can pass `false` to disable assigning default role
```ts title="auth.ts"
admin({
defaultRole: false //pass false to disable default role assignment
})
```
### Admin role
You can specify what role should be consider admin. Default to `admin`
```ts title="auth.ts"
admin({
adminRole: ["admin", "superAdmin"]
})
```
### impersonationSessionDuration
The duration of the impersonation session in seconds. Defaults to 1 hour.
```ts title="auth.ts"
admin({
impersonationSessionDuration: 60 * 60 * 24 // 1 day
})
```
### Default Ban Reason
The default ban reason for a user created by the admin. Defaults to `No reason`.
```ts title="auth.ts"
admin({
defaultBanReason: "Spamming"
})
```
### Default Ban Expires In
The default ban expires in for a user created by the admin in seconds. Defaults to `undefined` (meaning the ban never expires).
```ts title="auth.ts"
admin({
defaultBanExpiresIn: 60 * 60 * 24 // 1 day
})
```
file: ./content/docs/plugins/anonymous.mdx
meta: {
"title": "Anonymous",
"description": "Anonymous plugin for Better Auth."
}
The Anonymous plugin allows users to have an authenticated experience without requiring them to provide an email address, password, OAuth provider, or any other Personally Identifiable Information (PII). Users can later link an authentication method to their account when ready.
## Installation
### Add the plugin to your auth config
To enable anonymous authentication, add the anonymous plugin to your authentication configuration.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { anonymous } from "better-auth/plugins" // [!code highlight]
export const auth = betterAuth({
// ... other config options
plugins: [
anonymous() // [!code highlight]
]
})
```
### Migrate the database
Run the migration or generate the schema to add the necessary fields and tables to the database.
```bash
npx @better-auth/cli migrate
```
```bash
npx @better-auth/cli generate
```
See the [Schema](#schema) section to add the fields manually.
### Add the client plugin
Next, include the anonymous client plugin in your authentication client instance.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
import { anonymousClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [
anonymousClient()
]
})
```
## Usage
### Sign In
To sign in a user anonymously, use the `signIn.anonymous()` method.
```ts title="example.ts"
const user = await authClient.signIn.anonymous()
```
### Link Account
If a user is already signed in anonymously and tries to `signIn` or `signUp` with another method, their anonymous activities can linked to the new account.
To do that you first need to provide `onLinkAccount` callback to the plugin.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
plugins: [
anonymous({
onLinkAccount: async ({ anonymousUser, newUser }) => {
// perform actions like moving the cart items from anonymous user to the new user
}
})
]
```
Then when you call `signIn` or `signUp` with another method, the `onLinkAccount` callback will be called. And the `anonymousUser` will be deleted by default.
```ts title="example.ts"
const user = await authClient.signIn.email({
email,
})
```
## Options
* `emailDomainName`: The domain name to use when generating an email address for anonymous users. Defaults to the domain name of the current site.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
plugins: [
anonymous({
emailDomainName: "example.com"
})
]
})
```
* `onLinkAccount`: A callback function that is called when an anonymous user links their account to a new authentication method. The callback receives an object with the `anonymousUser` and the `newUser`.
* `disableDeleteAnonymousUser`: By default, the anonymous user is deleted when the account is linked to a new authentication method. Set this option to `true` to disable this behavior.
## Schema
The anonymous plugin requires an additional field in the user table:
file: ./content/docs/plugins/bearer.mdx
meta: {
"title": "Bearer Token Authentication",
"description": "Authenticate API requests using Bearer tokens instead of browser cookies"
}
The Bearer plugin enables authentication using Bearer tokens as an alternative to browser cookies. It intercepts requests, adding the Bearer token to the Authorization header before forwarding them to your API.
Use this cautiously; it is intended only for APIs that don't support cookies or require Bearer tokens for authentication. Improper implementation could easily lead to security vulnerabilities.
## Installing the Bearer Plugin
Add the Bearer plugin to your authentication setup:
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { bearer } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [bearer()]
});
```
## How to Use Bearer Tokens
### 1. Obtain the Bearer Token
After a successful sign-in, you'll receive a session token in the response headers. Store this token securely (e.g., in `localStorage`):
```ts title="auth-client.ts"
const { data } = await authClient.signIn.email({
email: "user@example.com",
password: "securepassword"
}, {
onSuccess: (ctx)=>{
const authToken = ctx.response.headers.get("set-auth-token") // get the token from the response headers
// Store the token securely (e.g., in localStorage)
localStorage.setItem("bearer_token", authToken);
}
});
```
You can also set this up globally in your auth client:
```ts title="auth-client.ts"
export const authClient = createAuthClient({
fetchOptions: {
onSuccess: (ctx) => {
const authToken = ctx.response.headers.get("set-auth-token") // get the token from the response headers
// Store the token securely (e.g., in localStorage)
if(authToken){
localStorage.setItem("bearer_token", authToken);
}
}
}
});
```
You may want to clear the token based on the response status code or other conditions:
### 2. Configure the Auth Client
Set up your auth client to include the Bearer token in all requests:
```ts title="auth-client.ts"
export const authClient = createAuthClient({
fetchOptions: {
auth: {
type:"Bearer",
token: () => localStorage.getItem("bearer_token") || "" // get the token from localStorage
}
}
});
```
### 3. Make Authenticated Requests
Now you can make authenticated API calls:
```ts title="auth-client.ts"
// This request is automatically authenticated
const { data } = await authClient.listSessions();
```
### 4. Per-Request Token (Optional)
You can also provide the token for individual requests:
```ts title="auth-client.ts"
const { data } = await authClient.listSessions({
fetchOptions: {
headers: {
Authorization: `Bearer ${token}`
}
}
});
```
### 5. Using Bearer Tokens Outside the Auth Client
The Bearer token can be used to authenticate any request to your API, even when not using the auth client:
```ts title="api-call.ts"
const token = localStorage.getItem("bearer_token");
const response = await fetch("https://api.example.com/data", {
headers: {
Authorization: `Bearer ${token}`
}
});
const data = await response.json();
```
And in the server, you can use the `auth.api.getSession` function to authenticate requests:
```ts title="server.ts"
import { auth } from "@/auth";
export async function handler(req, res) {
const session = await auth.api.getSession({
headers: req.headers
});
if (!session) {
return res.status(401).json({ error: "Unauthorized" });
}
// Process authenticated request
// ...
}
```
## Options
**requireSignature** (boolean): Require the token to be signed. Default: `false`.
file: ./content/docs/plugins/community-plugins.mdx
meta: {
"title": "Community Plugins",
"description": "A list of recommended community plugins."
}
This page showcases a list of recommended community made plugins.
We encourage you to create custom plugins and maybe get added to the list!
To create your own custom plugin, get started by reading our [plugins documentation](https://www.better-auth.com/docs/concepts/plugins). And if you want to share your plugin with the community, please open a pull request to add it to this list.
|
Plugin
| Description |
Author
|
| ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [better-auth-harmony](https://github.com/gekorm/better-auth-harmony/) | Email & phone normalization and additional validation, blocking over 55,000 temporary email domains. | [GeKorm](https://github.com/GeKorm) |
| [validation-better-auth](https://github.com/Daanish2003/validation-better-auth) | Validate API request using any validation library (e.g., Zod, Yup) | [Daanish2003](https://github.com/Daanish2003) |
file: ./content/docs/plugins/email-otp.mdx
meta: {
"title": "Email OTP",
"description": "Email OTP plugin for Better Auth."
}
The Email OTP plugin allows user to sign-in, verify their email, or reset their password using a one-time password (OTP) sent to their email address.
## Installation
### Add the plugin to your auth config
To enable email otp in your app, you need to add the `emailOTP` plugin to your auth config.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { emailOTP } from "better-auth/plugins" // [!code highlight]
export const auth = betterAuth({
// ... other config options
plugins: [
emailOTP({ // [!code highlight]
async sendVerificationOTP({ email, otp, type}) { // [!code highlight]
// Implement the sendVerificationOTP method to send the OTP to the user's email address // [!code highlight]
}, // [!code highlight]
}) // [!code highlight]
]
})
```
### Add the client plugin
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
import { emailOTPClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [
emailOTPClient()
]
})
```
## Usage
### Send OTP
First, send an OTP to the user's email address.
```ts title="example.ts"
const { data, error } = await authClient.emailOtp.sendVerificationOtp({
email: "user-email@email.com",
type: "sign-in" // or "email-verification", "forget-password"
})
```
### SignIn with OTP
Once the user provides the OTP, you can sign in the user using the `signIn.emailOTP()` method.
```ts title="example.ts"
const { data, error } = await authClient.signIn.emailOtp({
email: "user-email@email.com",
otp: "123456"
})
```
If the user is not registered, they'll be automatically registered. If you want to prevent this, you can pass `disableSignUp` as `true` in the options.
### Verify Email
To verify the user's email address, use the `verifyEmail()` method.
```ts title="example.ts"
const { data, error } = await authClient.emailOtp.verifyEmail({
email: "user-email@email.com",
otp: "123456"
})
```
### Reset Password
To reset the user's password, use the `resetPassword()` method.
```ts title="example.ts"
const { data, error } = await authClient.emailOtp.resetPassword({
email: "user-email@email.com",
otp: "123456",
password: "password"
})
```
## Options
* `sendVerificationOTP`: A function that sends the OTP to the user's email address. The function receives an object with the following properties:
* `email`: The user's email address.
* `otp`: The OTP to send.
* `type`: The type of OTP to send. Can be "sign-in", "email-verification", or "forget-password".
### Example
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
plugins: [
emailOTP({
async sendVerificationOTP({
email,
otp,
type
}) {
if (type === "sign-in") {
// Send the OTP for sign-in
} else if (type === "email-verification") {
// Send the OTP for email verification
} else {
// Send the OTP for password reset
}
},
})
]
})
```
* `otpLength`: The length of the OTP. Defaults to `6`.
* `expiresIn`: The expiry time of the OTP in seconds. Defaults to `300` seconds.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
plugins: [
emailOTP({
otpLength: 8,
expiresIn: 600
})
]
})
```
* `sendVerificationOnSignUp`: A boolean value that determines whether to send the OTP when a user signs up. Defaults to `false`.
* `disableSignUp`: A boolean value that determines whether to prevent automatic sign-up when the user is not registered. Defaults to `false`.
* `generateOTP`: A function that generates the OTP. Defaults to a random 6-digit number.
file: ./content/docs/plugins/generic-oauth.mdx
meta: {
"title": "Generic OAuth",
"description": "Authenticate users with any OAuth provider"
}
The Generic OAuth plugin provides a flexible way to integrate authentication with any OAuth provider. It supports both OAuth 2.0 and OpenID Connect (OIDC) flows, allowing you to easily add social login or custom OAuth authentication to your application.
## 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()
]
})
```
## Usage
The Generic OAuth plugin provides endpoints for initiating the OAuth flow and handling the callback. Here's how to use them:
### Initiate OAuth Sign-In
To start the OAuth sign-in process:
```ts title="sign-in.ts"
const response = await authClient.signIn.oauth2({
providerId: "provider-id",
callbackURL: "/dashboard" // the path to redirect to after the user is authenticated
});
```
### Linking OAuth Accounts
To link an OAuth account to an existing user:
```ts title="link-account.ts"
const response = await authClient.oauth2.link({
providerId: "provider-id",
callbackURL: "/dashboard" // the path to redirect to after the account is linked
});
```
### Handle OAuth Callback
The plugin mounts a route to handle the OAuth callback `/oauth2/callback/:providerId`. This means by default `${baseURL}/api/auth/oauth2/callback/:providerId` will be used as the callback URL. Make sure your OAuth provider is configured to use this URL.
## Configuration
When adding the plugin to your auth config, you can configure multiple OAuth providers. Each provider configuration object supports the following options:
```ts
interface GenericOAuthConfig {
providerId: string;
discoveryUrl?: string;
authorizationUrl?: string;
tokenUrl?: string;
userInfoUrl?: string;
clientId: string;
clientSecret: string;
scopes?: string[];
redirectURI?: string;
responseType?: string;
prompt?: string;
pkce?: boolean;
accessType?: string;
getUserInfo?: (tokens: OAuth2Tokens) => Promise;
}
```
* `providerId`: A unique identifier for the OAuth provider.
* `discoveryUrl`: URL to fetch OAuth 2.0 configuration (optional, but recommended for OIDC providers).
* `type`: Type of OAuth flow ("oauth2" or "oidc", defaults to "oauth2").
* `authorizationUrl`: URL for the authorization endpoint (optional if using discoveryUrl).
* `tokenUrl`: URL for the token endpoint (optional if using discoveryUrl).
* `userInfoUrl`: URL for the user info endpoint (optional if using discoveryUrl).
* `clientId`: OAuth client ID.
* `clientSecret`: OAuth client secret.
* `scopes`: Array of OAuth scopes to request.
* `redirectURI`: Custom redirect URI (optional).
* `responseType`: OAuth response type (defaults to "code").
* `prompt`: Controls the authentication experience for the user.
* `pkce`: Whether to use PKCE (Proof Key for Code Exchange, defaults to false).
* `accessType`: Access type for the authorization request.
* `getUserInfo`: Custom function to fetch user info (optional).
## Advanced Usage
### Custom User Info Fetching
You can provide a custom `getUserInfo` function to handle specific provider requirements:
```ts
genericOAuth({
config: [
{
providerId: "custom-provider",
// ... other config options
getUserInfo: async (tokens) => {
// Custom logic to fetch and return user info
const userInfo = await fetchUserInfoFromCustomProvider(tokens);
return {
id: userInfo.sub,
email: userInfo.email,
name: userInfo.name,
// ... map other fields as needed
};
}
}
]
})
```
### Map User Info Fields
If the user info returned by the provider does not match the expected format, or you need to map additional fields, you can use the `mapProfileToUser`:
```ts
genericOAuth({
config: [
{
providerId: "custom-provider",
// ... other config options
mapProfileToUser: async (profile) => {
return {
firstName: profile.given_name,
// ... map other fields as needed
};
}
}
]
})
```
### Error Handling
The plugin includes built-in error handling for common OAuth issues. Errors are typically redirected to your application's error page with an appropriate error message in the URL parameters. If the callback URL is not provided, the user will be redirected to Better Auth's default error page.
file: ./content/docs/plugins/jwt.mdx
meta: {
"title": "JWT",
"description": "Authenticate users with JWT tokens in services that can't use the session"
}
The JWT plugin provides endpoints to retrieve a JWT token and a JWKS endpoint to verify the token.
This plugin is not meant as a replacement for the session.
It's meant to be used for services that can't use the session.
## Installation
### Add the plugin to your **auth** config
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { jwt } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [ // [!code highlight]
jwt(), // [!code highlight]
] // [!code highlight]
})
```
### Migrate the database
Run the migration or generate the schema to add the necessary fields and tables to the database.
```bash
npx @better-auth/cli migrate
```
```bash
npx @better-auth/cli generate
```
See the [Schema](#schema) section to add the fields manually.
## Usage
Once you've installed the plugin, you can start using the JWT & JWKS plugin to get the token and the JWKS through their respective endpoints.
## JWT
### Retrieve the token
To get the token, call the `/token`, this will return the following:
```json
{
"token": "ey..."
}
```
Make sure to include the token in the Authorization header of your requests.
```ts
await fetch("/api/auth/token", {
headers: {
"Authorization": `Bearer ${token}`
},
})
```
### Verifying the token
The token can be verified in your own service, without the need for an additional verify call or database check.
For this JWKS is used, the public key can be fetched from the `/api/auth/jwks` endpoint.
Since this key is not subject to a frequent change, it can be cached indefinitely.
The Key ID (kid) that was used to sign a JWT is included in the header of the token.
In the case a JWT with a different kid is received it is recommended to fetch the JWKS again.
```json
{
"keys": [
{
"crv": "Ed25519",
"x": "bDHiLTt7u-VIU7rfmcltcFhaHKLVvWFy-_csKZARUEU",
"kty": "OKP",
"kid": "c5c7995d-0037-4553-8aee-b5b620b89b23"
}
]
}
```
## Schema
The JWT plugin adds the following tables to the database:
### JWKS
Table Name: `jwks`
## Options
### Algorithm of the Key Pair
The algorithm used for the generation of the keypair. The default is **EdDSA** with the **Ed25519** curve. Below are the available options:
```ts title="auth.ts"
jwt({
jwks: {
keyPairConfig: {
alg: "EdDSA",
crv: "Ed25519"
}
}
})
```
#### EdDSA
* **Default Curve**: `Ed25519`
* **Optional Property**: `crv`
* Available options: `Ed25519`, `Ed448`
* Default: `Ed25519`
#### ES256
* No additional properties
#### RSA256
* **Optional Property**: `modulusLength`
* Expects a number
* Default: `2048`
#### PS256
* **Optional Property**: `modulusLength`
* Expects a number
* Default: `2048`
#### ECDH-ES
* **Optional Property**: `crv`
* Available options: `P-256`, `P-384`, `P-521`
* Default: `P-256`
#### ES512
* No additional properties
### Disable private key encryption
By default, the private key is encrypted using AES256 GCM. You can disable this by setting the `disablePrivateKeyEncryption` option to `true`.
For security reasons, it's recommended to keep the private key encrypted.
```ts title="auth.ts"
jwt({
jwks: {
disablePrivateKeyEncryption: true
}
})
```
### Modify JWT payload
By default the entire user object is added to the JWT payload. You can modify the payload by providing a function to the `definePayload` option.
```ts title="auth.ts"
jwt({
jwt: {
definePayload: (user) => {
return {
id: user.id,
email: user.email,
role: user.role
}
}
}
})
```
### Modify Issuer, Audience or Expiration time
If none is given, the BASE\_URL is used as the issuer and the audience is set to the BASE\_URL. The expiration time is set to 15 minutes.
```ts title="auth.ts"
jwt({
jwt: {
issuer: "https://example.com",
audience: "https://example.com",
expirationTime: "1h"
}
})
```
file: ./content/docs/plugins/magic-link.mdx
meta: {
"title": "Magic link",
"description": "Magic link plugin"
}
Magic link or email link is a way to authenticate users without a password. When a user enters their email, a link is sent to their email. When the user clicks on the link, they are authenticated.
## Installation
### Add the server Plugin
Add the magic link plugin to your server:
```ts title="server.ts"
import { betterAuth } from "better-auth";
import { magicLink } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
magicLink({
sendMagicLink: async ({ email, token, url }, request) => {
// send email to user
}
})
]
})
```
### Add the client Plugin
Add the magic link plugin to your client:
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client";
import { magicLinkClient } from "better-auth/client/plugins";
const authClient = createAuthClient({
plugins: [
magicLinkClient()
]
});
```
## Usage
### Sign In with Magic Link
To sign in with a magic link, you need to call `signIn.magicLink` with the user's email address. The `sendMagicLink` function is called to send the magic link to the user's email.
```ts title="magic-link.ts"
const { data, error } = await authClient.signIn.magicLink({
email: "user@email.com",
callbackURL: "/dashboard", //redirect after successful login (optional)
});
```
If the user has not signed up, unless `disableSignUp` is set to `true`, the user will be signed up automatically.
### Verify Magic Link
When you send the URL generated by the `sendMagicLink` function to a user, clicking the link will authenticate them and redirect them to the `callbackURL` specified in the `signIn.magicLink` function. If an error occurs, the user will be redirected to the `callbackURL` with an error query parameter.
If no `callbackURL` is provided, the user will be redirected to the root URL.
If you want to handle the verification manually, (e.g, if you send the user a different url), you can use the `verify` function.
```ts title="magic-link.ts"
const { data, error } = await authClient.magicLink.verify({
query: {
token,
},
});
```
## Configuration Options
**sendMagicLink**: The `sendMagicLink` function is called when a user requests a magic link. It takes an object with the following properties:
* `email`: The email address of the user.
* `url`: The url to be sent to the user. This url contains the token.
* `token`: The token if you want to send the token with custom url.
and a `request` object as the second parameter.
**expiresIn**: specifies the time in seconds after which the magic link will expire. The default value is `300` seconds (5 minutes).
**disableSignUp**: If set to `true`, the user will not be able to sign up using the magic link. The default value is `false`.
**generateToken**: The `generateToken` function is called to generate a token which is used to uniquely identify the user. The default value is a random string. There is one parameter:
* `email`: The email address of the user.
When using `generateToken`, ensure that the returned string is hard to guess
because it is used to verify who someone actually is in a confidential way. By
default, we return a long and cryptographically secure string.
file: ./content/docs/plugins/multi-session.mdx
meta: {
"title": "Multi Session",
"description": "Learn how to use multi-session plugin in Better Auth."
}
The multi-session plugin allows users to maintain multiple active sessions across different accounts in the same browser. This plugin is useful for applications that require users to switch between multiple accounts without logging out.
## Installation
### Add the plugin to your **auth** config
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { multiSession } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [ // [!code highlight]
multiSession(), // [!code highlight]
] // [!code highlight]
})
```
### Add the client Plugin
Add the client plugin and Specify where the user should be redirected if they need to verify 2nd factor
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
import { multiSessionClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [
multiSessionClient()
]
})
```
## Usage
Whenever a user logs in, the plugin will add additional cookie to the browser. This cookie will be used to maintain multiple sessions across different accounts.
### List all device sessions
To list all active sessions for the current user, you can call the `listDeviceSessions` method.
```ts
await authClient.multiSession.listDeviceSessions()
```
on the server you can call `listDeviceSessions` method.
```ts
await auth.api.listDeviceSessions()
```
### Set active session
To set the active session, you can call the `setActive` method.
```ts
await authClient.multiSession.setActive({
sessionToken: "session-token"
})
```
### Revoke a session
To revoke a session, you can call the `revoke` method.
```ts
await authClient.multiSession.revoke({
sessionToken: "session-token"
})
```
### Revoke all sessions
To revoke all sessions, you can call the `revokeAll` method.
```ts
await authClient.multiSession.revokeAll();
```
### Signout Behaviour
When a user logs out, the plugin will revoke all active sessions for the user.
### Max Sessions
You can specify the maximum number of sessions a user can have by passing the `maximumSessions` option to the plugin. By default, the plugin allows 5 sessions per device.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
export const auth = betterAuth({
plugins: [
multiSession({
maximumSessions: 3
})
]
})
```
file: ./content/docs/plugins/oauth-proxy.mdx
meta: {
"title": "OAuth Proxy",
"description": "OAuth Proxy plugin for Better Auth"
}
A proxy plugin, that allows you to proxy OAuth requests. Useful for development and preview deployments where the redirect URL can't be known in advance to add to the OAuth provider.
## Installation
### Add the plugin to your **auth** config
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { oAuthProxy } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [ // [!code highlight]
oAuthProxy(), // [!code highlight]
] // [!code highlight]
})
```
### Add redirect URL to your OAuth provider
For the proxy server to work properly, you’ll need to pass the redirect URL of your main production app registered with the OAuth provider in your social provider config. This needs to be done for each social provider you want to proxy requests for.
```ts
export const auth = betterAuth({
plugins: [
oAuthProxy(),
],
socialProviders: {
github: {
clientId: "your-client-id",
clientSecret: "your-client-secret",
redirectURI: "https://my-main-app.com/api/auth/callback/github". // [!code highlight]
}
}
})
```
## How it works
The plugin adds an endpoint to your server that proxies OAuth requests. When you initiate a social sign-in, it sets the redirect URL to this proxy endpoint. After the OAuth provider redirects back to your server, the plugin then forwards the user to the original callback URL.
```ts
await authClient.signIn.social({
provider: "github",
callbackURL: "/dashboard" // the plugin will override this to something like "http://localhost:3000/api/auth/oauth-proxy?callbackURL=/dashboard"
})
```
When the OAuth provider returns the user to your server, the plugin automatically redirects them to the intended callback URL.
To share cookies between the proxy server and your main server it uses url query parameters to pass the cookies encrypted in the URL. This is secure as the cookies are encrypted and can only be decrypted by the server.
## Options
**currentURL**: The application's current URL is automatically determined by the plugin. It first it check for the request URL if invoked by a client, then it checks the base URL from popular hosting providers, and finally falls back to the `baseURL` in your auth config. If the URL isn’t inferred correctly, you can specify it manually here.
file: ./content/docs/plugins/oidc-provider.mdx
meta: {
"title": "OIDC Provider",
"description": "Open ID Connect plugin for Better Auth that allows you to have your own OIDC provider."
}
The **OIDC Provider Plugin** enables you to build and manage your own OpenID Connect (OIDC) provider, granting full control over user authentication without relying on third-party services like Okta or Azure AD. It also allows other services to authenticate users through your OIDC provider.
**Key Features**:
* **Client Registration**: Register clients to authenticate with your OIDC provider.
* **Dynamic Client Registration**: Allow clients to register dynamically.
* **Authorization Code Flow**: Support the Authorization Code Flow.
* **JWKS Endpoint**: Publish a JWKS endpoint to allow clients to verify tokens. (Not fully implemented)
* **Refresh Tokens**: Issue refresh tokens and handle access token renewal using the `refresh_token` grant.
* **OAuth Consent**: Implement OAuth consent screens for user authorization, with an option to bypass consent for trusted applications.
* **UserInfo Endpoint**: Provide a UserInfo endpoint for clients to retrieve user details.
This plugin is in active development and may not be suitable for production use. Please report any issues or bugs on [GitHub](https://github.com/better-auth/better-auth).
## Installation
### Mount the Plugin
Add the OIDC plugin to your auth config. See [OIDC Configuration](#oidc-configuration) on how to configure the plugin.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { oidcProvider } from "better-auth/plugins";
const auth = betterAuth({
plugins: [oidcProvider({
loginPage: "/sign-in", // path to the login page
// ...other options
})]
})
```
### Migrate the Database
Run the migration or generate the schema to add the necessary fields and tables to the database.
```bash
npx @better-auth/cli migrate
```
```bash
npx @better-auth/cli generate
```
See the [Schema](#schema) section to add the fields manually.
### Add the Client Plugin
Add the OIDC client plugin to your auth client config.
```ts
import { createAuthClient } from "better-auth/client";
import { oidcClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [oidcClient({
// Your OIDC configuration
})]
})
```
## Usage
Once installed, you can utilize the OIDC Provider to manage authentication flows within your application.
### Register a New Client
To register a new OIDC client, use the `oauth2.register` method.
```ts title="client.ts"
const application = await client.oauth2.register({
name: "My Client",
redirectURLs: ["https://client.example.com/callback"],
});
```
Once the application is created, you will receive a `clientId` and `clientSecret` that you can display to the user.
### Consent Screen
When a user is redirected to the OIDC provider for authentication, they may be prompted to authorize the application to access their data. This is known as the consent screen. By default, Better Auth will display a sample consent screen. You can customize the consent screen by providing a `consentPage` option during initialization.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
plugins: [oidcProvider({
consentPage: "/path/to/consent/page"
})]
})
```
The plugin will redirect the user to the specified path with a `client_id` and `scope` query parameter. You can use this information to display a custom consent screen. Once the user consents, you can call `oauth2.consent` to complete the authorization.
```ts title="server.ts"
const res = await client.oauth2.consent({
accept: true, // or false to deny
});
```
The `client_id` and other necessary information are stored in the browser cookie, so you don't need to pass them in the request. If they don't exist in the cookie, the consent method will return an error.
### Handling Login
When a user is redirected to the OIDC provider for authentication, if they are not already logged in, they will be redirected to the login page. You can customize the login page by providing a `loginPage` option during initialization.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
export const auth = betterAuth({
plugins: [oidcProvider({
loginPage: "/sign-in"
})]
})
```
You don't need to handle anything from your side; when a new session is created, the plugin will handle continuing the authorization flow.
## Configuration
### OIDC Metadata
Customize the OIDC metadata by providing a configuration object during initialization.
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { oidcProvider } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [oidcProvider({
metadata: {
issuer: "https://your-domain.com",
authorization_endpoint: "/custom/oauth2/authorize",
token_endpoint: "/custom/oauth2/token",
// ...other custom metadata
}
})]
})
```
### JWKS Endpoint (Not Fully Implemented)
For JWKS support, you need to use the `jwt` plugin. It exposes the `/jwks` endpoint to provide the public keys.
Currently, the token is signed with the application's secret key. The JWKS endpoint is not fully implemented yet.
### Dynamic Client Registration
If you want to allow clients to register dynamically, you can enable this feature by setting the `allowDynamicClientRegistration` option to `true`.
```ts title="auth.ts"
const auth = betterAuth({
plugins: [oidcProvider({
allowDynamicClientRegistration: true,
})]
})
```
This will allow clients to register using the `/register` endpoint to be publicly available.
## Schema
The OIDC Provider plugin adds the following tables to the database:
### OAuth Application
Table Name: `oauthApplication`
### OAuth Access Token
Table Name: `oauthAccessToken`
### OAuth Consent
Table Name: `oauthConsent`
## Options
**allowDynamicClientRegistration**: `boolean` - Enable or disable dynamic client registration.
**metadata**: `OIDCMetadata` - Customize the OIDC provider metadata.
**loginPage**: `string` - Path to the custom login page.
**consentPage**: `string` - Path to the custom consent page.
file: ./content/docs/plugins/one-tap.mdx
meta: {
"title": "One Tap",
"description": "One Tap plugin for Better Auth"
}
The One Tap plugin allows users to log in with a single tap using Google's One Tap API. The plugin
provides a simple way to integrate One Tap into your application, handling the client-side and server-side logic for you.
## Installation
### Add the Server Plugin
Add the One Tap plugin to your auth configuration:
```ts title="auth.ts"
import { betterAuth } from "better-auth";
import { oneTap } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
oneTap(), // Add the One Tap server plugin
]
});
```
### Add the Client Plugin
Add the client plugin and specify where the user should be redirected after sign-in or if additional verification (like 2FA) is needed.
import { createAuthClient } from "better-auth/client";
import { oneTapClient } from "better-auth/client/plugins";
```ts
const authClient = createAuthClient({
plugins: [
oneTapClient({
clientId: "YOUR_CLIENT_ID",
// Optional client configuration:
autoSelect: false,
cancelOnTapOutside: true,
context: "signin",
additionalOptions: {
// Any extra options for the Google initialize method
},
// Configure prompt behavior and exponential backoff:
promptOptions: {
baseDelay: 1000, // Base delay in ms (default: 1000)
maxAttempts: 5 // Maximum number of attempts before triggering onPromptNotification (default: 5)
}
})
]
});
```
### Usage
To display the One Tap popup, simply call the oneTap method on your auth client:
```ts
await authClient.oneTap();
```
### Customizing Redirect Behavior
By default, after a successful login the plugin will hard redirect the user to `/`. You can customize this behavior as follows:
#### Avoiding a Hard Redirect
Pass fetchOptions with an onSuccess callback to handle the login response without a page reload:
```ts
authClient.oneTap({
fetchOptions: {
onSuccess: () => {
// For example, use a router to navigate without a full reload:
router.push("/dashboard");
}
}
});
```
#### Specifying a Custom Callback URL
To perform a hard redirect to a different page after login, use the callbackURL option:
```ts
authClient.oneTap({
callbackURL: "/dashboard"
});
```
#### Handling Prompt Dismissals with Exponential Backoff
If the user dismisses or skips the prompt, the plugin will retry showing the One Tap prompt using exponential backoff based on your configured promptOptions.
If the maximum number of attempts is reached without a successful sign-in, you can use the onPromptNotification callback to be notified—allowing you to render an alternative UI (e.g., a traditional Google Sign-In button) so users can restart the process manually:
```ts
authClient.oneTap({
onPromptNotification: (notification) => {
console.warn("Prompt was dismissed or skipped. Consider displaying an alternative sign-in option.", notification);
// Render your alternative UI here
}
});
```
### Client Options
* **clientId**: The client ID for your Google One Tap API.
* **autoSelect**: Automatically select the account if the user is already signed in. Default is false.
* **context**: The context in which the One Tap API should be used (e.g., "signin"). Default is "signin".
* **cancelOnTapOutside**: Cancel the One Tap popup when the user taps outside it. Default is true.
* additionalOptions: Extra options to pass to Google's initialize method as per the [Google Identity Services docs](https://developers.google.com/identity/gsi/web/reference/js-reference#google.accounts.id.prompt).
* **promptOptions**: Configuration for the prompt behavior and exponential backoff:
* **baseDelay**: Base delay in milliseconds for retries. Default is 1000.
* **maxAttempts**: Maximum number of prompt attempts before invoking the onPromptNotification callback. Default is 5.
### Server Options
* **disableSignUp**: Disable the sign-up option, allowing only existing users to sign in. Default is `false`.
* **ClientId**: Optionally, pass a client ID here if it is not provided in your social provider configuration.
file: ./content/docs/plugins/open-api.mdx
meta: {
"title": "Open API",
"description": "Open API reference for Better Auth."
}
This is a plugin that provides an Open API reference for Better Auth. It shows all endpoints added by plugins and the core. It also provides a way to test the endpoints. It uses [Scalar](https://scalar.com/) to display the Open API reference.
This plugin is still in the early stages of development. We are working on adding more features to it and filling in the gaps.
## Installation
### Add the plugin to your **auth** config
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { openAPI } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [ // [!code highlight]
openAPI(), // [!code highlight]
] // [!code highlight]
})
```
### Navigate to `/api/auth/reference` to view the Open API reference
Each plugin endpoints are grouped by the plugin name. The core endpoints are grouped under the `Default` group. And Model schemas are grouped under the `Models` group.

## Usage
The Open API reference is generated using the [OpenAPI 3.0](https://swagger.io/specification/) specification. You can use the reference to generate client libraries, documentation, and more.
The reference is generated using the [Scalar](https://scalar.com/) library. Scalar provides a way to view and test the endpoints. You can test the endpoints by clicking on the `Try it out` button and providing the required parameters.

### Generated Schema
To get the generated Open API schema directly as JSON, you can do `auth.api.generateOpenAPISchema()`. This will return the Open API schema as a JSON object.
```ts
import { auth } from "~/lib/auth"
const openAPISchema = await auth.api.generateOpenAPISchema()
console.log(openAPISchema)
```
## Configuration
`path` - The path where the Open API reference is served. Default is `/api/auth/reference`. You can change it to any path you like, but keep in mind that it will be appended to the base path of your auth server.
`disableDefaultReference` - If set to `true`, the default Open API reference UI by Scalar will be disabled. Default is `false`.
file: ./content/docs/plugins/organization.mdx
meta: {
"title": "Organization",
"description": "The organization plugin allows you to manage your organization's members and teams."
}
Organizations simplifies user access and permissions management. Assign roles and permissions to streamline project management, team coordination, and partnerships.
## Installation
### Add the plugin to your **auth** config
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [ // [!code highlight]
organization() // [!code highlight]
] // [!code highlight]
})
```
### Migrate the database
Run the migration or generate the schema to add the necessary fields and tables to the database.
```bash
npx @better-auth/cli migrate
```
```bash
npx @better-auth/cli generate
```
See the [Schema](#schema) section to add the fields manually.
### Add the client plugin
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
import { organizationClient } from "better-auth/client/plugins"
const client = createAuthClient({
plugins: [ // [!code highlight]
organizationClient() // [!code highlight]
] // [!code highlight]
})
```
## Usage
Once you've installed the plugin, you can start using the organization plugin to manage your organization's members and teams. The client plugin will provide you methods under the `organization` namespace. And the server `api` will provide you with the necessary endpoints to manage your organization and gives you easier way to call the functions on your own backend.
## Organization
### Create an organization
To create an organization, you need to provide:
* `name`: The name of the organization.
* `slug`: The slug of the organization.
* `logo`: The logo of the organization. (Optional)
```ts title="auth-client.ts"
await authClient.organization.create({
name: "My Organization",
slug: "my-org",
logo: "https://example.com/logo.png"
})
```
#### Restrict who can create an organization
By default, any user can create an organization. To restrict this, set the `allowUserToCreateOrganization` option to a function that returns a boolean, or directly to `true` or `false`.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
const auth = betterAuth({
//...
plugins: [
organization({
allowUserToCreateOrganization: async (user) => { // [!code highlight]
const subscription = await getSubscription(user.id) // [!code highlight]
return subscription.plan === "pro" // [!code highlight]
} // [!code highlight]
})
]
})
```
### List User's Organizations
To list the organizations that a user is a member of, you can use `useListOrganizations` hook. It implements a reactive way to get the organizations that the user is a member of.
```tsx title="client.tsx"
import { client } from "@/auth/client"
function App(){
const { data: organizations } = client.useListOrganizations()
return (
{organizations.map(org =>
{org.name}
)}
)
}
```
```svelte title="page.svelte"
Organizations
s
{#if $organizations.isPending}
Loading...
{:else if $organizations.data === null}
No organizations found.
{:else}
{#each $organizations.data as organization}
{organization.name}
{/each}
{/if}
```
```vue title="organization.vue"
Organizations
Loading...
No organizations found.
{{ organization.name }}
```
### Active Organization
Active organization is the workspace the user is currently working on. By default when the user is signed in the active organization is set to `null`. You can set the active organization to the user session.
It's not always you want to persist the active organization in the session. You can manage the active organization in the client side only. For example, multiple tabs can have different active organizations.
#### Set Active Organization
You can set the active organization by calling the `organization.setActive` function. It'll set the active organization for the user session.
```ts title="auth-client.ts"
import { client } from "@/lib/auth-client";
await authClient.organization.setActive({
organizationId: "organization-id"
})
// you can also use organizationSlug instead of organizationId
authClient.organization.setActive({
organizationSlug: "organization-slug"
})
```
```ts title="api.ts"
import { auth } from "@/lib/auth";
auth.api.setActiveOrganization({
headers: // pass the headers,
body: {
organizationSlug: "organization-slug"
}
})
// you can also use organizationId instead of organizationSlug
auth.api.setActiveOrganization({
headers: // pass the headers,
body: {
organizationId: "organization-id"
}
})
```
To set active organization when a session is created you can use [database hooks](/docs/concepts/database#database-hooks).
```ts title="auth.ts"
export const auth = betterAuth({
databaseHooks: {
session: {
create: {
before: async(session)=>{
const organization = await getActiveOrganization(session.userId)
return {
data: {
...session,
activeOrganizationId: organization.id
}
}
}
}
}
}
})
```
#### Use Active Organization
To retrieve the active organization for the user, you can call the `useActiveOrganization` hook. It returns the active organization for the user. Whenever the active organization changes, the hook will re-evaluate and return the new active organization.
```tsx title="client.tsx"
import { client } from "@/auth/client"
function App(){
const { data: activeOrganization } = client.useActiveOrganization()
return (
{activeOrganization ?
{activeOrganization.name}
: null}
)
}
```
```tsx title="client.tsx"
Active Organization
{#if $activeOrganization.isPending}
Loading...
{:else if $activeOrganization.data === null}
No active organization found.
{:else}
{$activeOrganization.data.name}
{/if}
```
```vue title="organization.vue"
Active organization
Loading...
No active organization.
{{ activeOrganization.data.name }}
```
### Get Full Organization
To get the full details of an organization, you can use the `getFullOrganization` function provided by the client. The function takes an object with the following properties:
* `organizationId`: The id of the organization. (Optional) – By default, it will use the active organization.
* `organizationSlug`: The slug of the organization. (Optional) – To get the organization by slug.
```ts title="auth-client.ts"
const organization = await authClient.organization.getFullOrganization({
organizationId: "organization-id" // optional, by default it will use the active organization
})
//you can also use organizationSlug instead of organizationId
const organization = await authClient.organization.getFullOrganization({
organizationSlug: "organization-slug"
})
```
```ts title="api.ts"
import { auth } from "@/auth";
auth.api.getFullOrganization({
headers: // pass the headers
})
// you can also use organizationSlug instead of organizationId
auth.api.getFullOrganization({
headers: // pass the headers,
query: {
organizationSlug: "organization-slug"
}
})
```
### Update Organization
To update organization info, you can use `organization.update`
```ts
await client.organization.update({
data: {
name: "updated-name",
logo: "new-logo.url",
metadata: {
customerId: "test"
},
slug: "updated-slug"
},
organizationId: 'org-id' //defaults to the current active organization
})
```
### Delete Organization
To remove user owned organization, you can use `organization.delete`
```ts title="org.ts"
await authClient.organization.delete({
organizationId: "test"
});
```
If the user has the necessary permissions (by default: role is owner) in the specified organization, all members, invitations and organization information will be removed.
You can configure how organization deletion is handled through `organizationDeletion` option:
```ts
const auth = betterAuth({
organizationDeletion: {
disabled: true, //to disable it altogether
beforeDelete: async(data, request)=>{
// a callback to run before deleting org
},
afterDelete: async(data, request)=>{
// a callback to run after deleting org
}
}
})
```
## Invitations
To add a member to an organization, we first need to send an invitation to the user. The user will receive an email/sms with the invitation link. Once the user accepts the invitation, they will be added to the organization.
### Setup Invitation Email
For member invitation to work we first need to provider `sendInvitationEmail` to the `better-auth` instance. This function is responsible for sending the invitation email to the user.
You'll need to construct and send the invitation link to the user. The link should include the invitation ID, which will be used with the acceptInvitation function when the user clicks on it.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
import { sendOrganizationInvitation } from "./email"
export const auth = betterAuth({
plugins: [
organization({
async sendInvitationEmail(data) {
const inviteLink = `https://example.com/accept-invitation/${data.id}`
sendOrganizationInvitation({
email: data.email,
invitedByUsername: data.inviter.user.name,
invitedByEmail: data.inviter.user.email,
teamName: data.organization.name,
inviteLink
})
},
}),
],
});
```
### Send Invitation
To invite users to an organization, you can use the `invite` function provided by the client. The `invite` function takes an object with the following properties:
* `email`: The email address of the user.
* `role`: The role of the user in the organization. It can be `admin`, `member`, or `guest`.
* `organizationId`: The id of the organization. this is optional by default it will use the active organization. (Optional)
```ts title="invitation.ts"
await authClient.organization.inviteMember({
email: "test@email.com",
role: "admin",
})
```
### Accept Invitation
When a user receives an invitation email, they can click on the invitation link to accept the invitation. The invitation link should include the invitation ID, which will be used to accept the invitation.
Make sure to call the `acceptInvitation` function after the user is logged in.
```ts title="auth-client.ts"
await authClient.organization.acceptInvitation({
invitationId: "invitation-id"
})
```
### Update Invitation Status
To update the status of invitation you can use the `acceptInvitation`, `cancelInvitation`, `rejectInvitation` functions provided by the client. The functions take the invitation id as an argument.
```ts title="auth-client.ts"
//cancel invitation
await authClient.organization.cancelInvitation({
invitationId: "invitation-id"
})
//reject invitation (needs to be called when the user who received the invitation is logged in)
await authClient.organization.rejectInvitation({
invitationId: "invitation-id"
})
```
### Get Invitation
To get an invitation you can use the `getInvitation` function provided by the client. You need to provide the invitation id as a query parameter.
```ts title="auth-client.ts"
client.organization.getInvitation({
query: {
id: params.id
}
})
```
## Members
### Remove Member
To remove you can use `organization.removeMember`
```ts title="auth-client.ts"
//remove member
await authClient.organization.removeMember({
memberId: "member-id"
})
```
### Update Member Role
To update the role of a member in an organization, you can use the `organization.updateMemberRole`. If the user has the permission to update the role of the member, the role will be updated.
```ts title="auth-client.ts"
await authClient.organization.updateMemberRole({
memberId: "member-id",
role: "admin"
})
```
### Get Active Member
To get the current member of the organization you can use the `organization.getActiveMember` function. This function will return the current active member.
```ts title="auth-client.ts"
const member = await authClient.organization.getActiveMember()
```
### Add Member
If you want to add a member directly to an organization without sending an invitation, you can use the `addMember` function which can only be invoked on the server.
```ts title="api.ts"
import { auth } from "@/auth";
auth.api.addMember({
body: {
userId: "user-id",
organizationId: "organization-id",
role: "admin"
}
})
```
### Leave Organization
To leave organization you can use `organization.leave` function. This function will remove the current user from the organization.
```ts title="auth-client.ts"
await authClient.organization.leave({
organizationId: "organization-id"
})
```
## Access Control
The organization plugin providers a very flexible access control system. You can control the access of the user based on the role they have in the organization. You can define your own set of permissions based on the role of the user.
### Roles
By default, there are three roles in the organization:
`owner`: The user who created the organization by default. The owner has full control over the organization and can perform any action.
`admin`: Users with the admin role have full control over the organization except for deleting the organization or changing the owner.
`member`: Users with the member role have limited control over the organization. They can create projects, invite users, and manage projects they have created.
### Permissions
By default, there are three resources, and these have two to three actions.
**organization**:
`update` `delete`
**member**:
`create` `update` `delete`
**invitation**:
`create` `cancel`
The owner have full control over all the resources and actions. The admin have full control over all the resources except for deleting the organization or changing the owner. The member have no control over any of those action other than reading the data.
### Custom Permissions
the plugin providers easy way to define your own set of permission for each role.
#### Create Access Control
You first need to create access controller by calling `createAccessControl` function and passing the statement object. The statement object should have the resource name as the key and the array of actions as the value.
```ts title="permissions.ts"
import { createAccessControl } from "better-auth/plugins/access";
/**
* make sure to use `as const` so typescript can infer the type correctly
*/
const statement = { // [!code highlight]
project: ["create", "share", "update", "delete"], // [!code highlight]
} as const; // [!code highlight]
const ac = createAccessControl(statement); // [!code highlight]
```
#### Create Roles
Once you have created the access controller you can create roles with the permissions you have defined.
```ts title="permissions.ts"
import { createAccessControl } from "better-auth/plugins/access";
const statement = {
project: ["create", "share", "update", "delete"],
} as const;
const ac = createAccessControl(statement);
const member = ac.newRole({ // [!code highlight]
project: ["create"], // [!code highlight]
}); // [!code highlight]
const admin = ac.newRole({ // [!code highlight]
project: ["create", "update"], // [!code highlight]
}); // [!code highlight]
const owner = ac.newRole({ // [!code highlight]
project: ["create", "update", "delete"], // [!code highlight]
}); // [!code highlight]
const myCustomRole = ac.newRole({ // [!code highlight]
project: ["create", "update", "delete"], // [!code highlight]
organization: ["update"], // [!code highlight]
}); // [!code highlight]
```
When you create custom roles for existing roles, the predefined permissions for those roles will be overridden. To add the existing permissions to the custom role, you need to import `defaultStatement` and merge it with your new statement, plus merge the roles' permissions set with the default roles.
```ts title="permissions.ts"
import { createAccessControl, defaultStatements, adminAc } from "better-auth/plugins/access";
const statement = {
...defaultStatements, // [!code highlight]
project: ["create", "share", "update", "delete"],
} as const;
const ac = createAccessControl(statement);
const admin = ac.newRole({
project: ["create", "update"],
...adminAc.statements, // [!code highlight]
});
```
#### Pass Roles to the Plugin
Once you have created the roles you can pass them to the organization plugin both on the client and the server.
```ts title="auth.ts"
import { ac, owner, admin, member } from "@/auth/permissions"
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
export const auth = betterAuth({
plugins: [
organization({
ac: ac,
roles: {
owner,
admin,
member,
myCustomRole
}
}),
],
});
```
You also need to pass the access controller and the roles to the client plugin.
```ts title="auth-client"
import { createAuthClient } from "better-auth/client"
import { organizationClient } from "better-auth/client/plugins"
import { ac } from "@/auth/permissions"
export const client = createAuthClient({
plugins: [
organizationClient({
ac: ac,
roles: {
owner,
admin,
member,
myCustomRole
}
})
]
})
```
### Access Control Usage
**Has Permission**:
You can use the `hasPermission` action provided by the `api` to check the permission of the user.
```ts title="api.ts"
import { auth } from "@/auth";
auth.api.hasPermission({
headers: await headers(),
body: {
permission: {
project: ["create"] // This must match the structure in your access control
}
}
});
```
If you want to check the permission of the user on the client from the server you can use the `hasPermission` function provided by the client.
```ts title="auth-client.ts"
const canCreateProject = await authClient.organization.hasPermission({
permission: {
project: ["create"]
}
})
```
**Check Role Permission**:
Once you have defined the roles and permissions to avoid checking the permission from the server you can use the `checkRolePermission` function provided by the client.
```ts title="auth-client.ts"
const canCreateProject = client.organization.checkRolePermission({
permission: {
organization: ["delete"],
},
role: "admin",
});
```
## Schema
The organization plugin adds the following tables to the database:
### Organization
Table Name: `organization`
### Member
Table Name: `member`
### Invitation
Table Name: `invitation`
### Session
Table Name: `session`
You need to add one more field to the session table to store the active organization id.
### Customizing the Schema
To change the schema table name or fields, you can pass `schema` option to the organization plugin.
```ts title="auth.ts"
const auth = betterAuth({
plugins: [organization({
schema: {
organization: {
modelName: "organizations", //map the organization table to organizations
fields: {
name: "title" //map the name field to title
}
}
}
})]
})
```
## Options
**allowUserToCreateOrganization**: `boolean` | `((user: User) => Promise | boolean)` - A function that determines whether a user can create an organization. By default, it's `true`. You can set it to `false` to restrict users from creating organizations.
**organizationLimit**: `number` | `((user: User) => Promise | boolean)` - The maximum number of organizations allowed for a user. By default, it's `5`. You can set it to any number you want or a function that returns a boolean.
**creatorRole**: `admin | owner` - The role of the user who creates the organization. By default, it's `owner`. You can set it to `admin`.
**membershipLimit**: `number` - The maximum number of members allowed in an organization. By default, it's `100`. You can set it to any number you want.
**sendInvitationEmail**: `async (data) => Promise` - A function that sends an invitation email to the user.
**invitationExpiresIn** : `number` - How long the invitation link is valid for in seconds. By default, it's 48 hours (2 days).
file: ./content/docs/plugins/passkey.mdx
meta: {
"title": "Passkey",
"description": "Passkey"
}
Passkeys are a secure, passwordless authentication method using cryptographic key pairs, supported by WebAuthn and FIDO2 standards in web browsers. They replace passwords with unique key pairs: a private key stored on the user’s device and a public key shared with the website. Users can log in using biometrics, PINs, or security keys, providing strong, phishing-resistant authentication without traditional passwords.
The passkey plugin implementation is powered by [simple-web-authn](https://simplewebauthn.dev/) behind the scenes.
## Installation
### Add the plugin to your auth config
To add the passkey plugin to your auth config, you need to import the plugin and pass it to the `plugins` option of the auth instance.
**Options**
`rpID`: A unique identifier for your website. 'localhost' is okay for local dev
`rpName`: Human-readable title for your website
`origin`: The URL at which registrations and authentications should occur. '[http://localhost](http://localhost)' and '[http://localhost:PORT](http://localhost:PORT)' are also valid. Do **NOT** include any trailing /
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { passkey } from "better-auth/plugins/passkey" // [!code highlight]
export const auth = betterAuth({
plugins: [ // [!code highlight]
passkey(), // [!code highlight]
], // [!code highlight]
})
```
### Migrate the database
Run the migration or generate the schema to add the necessary fields and tables to the database.
```bash
npx @better-auth/cli migrate
```
```bash
npx @better-auth/cli generate
```
See the [Schema](#schema) section to add the fields manually.
### Add the client plugin
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
import { passkeyClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [ // [!code highlight]
passkeyClient() // [!code highlight]
] // [!code highlight]
})
```
## Usage
### Add/Register a passkey
To add or register a passkey make sure a user is authenticated and then call the `passkey.addPasskey` function provided by the client.
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client";
import { passkeyClient } from "better-auth/client/plugins";
const authClient = createAuthClient({
plugins: [
// [!code highlight]
passkeyClient(), // [!code highlight]
], // [!code highlight]
});
const data = await authClient.passkey.addPasskey();
```
This will prompt the user to register a passkey. And it'll add the passkey to the user's account.
### Signin with a passkey
To signin with a passkey you can use the passkeySignIn method. This will prompt the user to sign in with their passkey.
Signin method accepts:
`autoFill`: Browser autofill, a.k.a. Conditional UI. [read more](https://simplewebauthn.dev/docs/packages/browser#browser-autofill-aka-conditional-ui)
`callbackURL`: The URL to redirect to after the user has signed in. (optional)
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client";
import { passkeyClient } from "better-auth/client/plugins";
const authClient = createAuthClient({
plugins: [
// [!code highlight]
passkeyClient(), // [!code highlight]
], // [!code highlight]
});
const data = await authClient.signIn.passkey();
```
### Conditional UI
The plugin supports conditional UI, which allows the browser to autofill the passkey if the user has already registered a passkey.
There are two requirements for conditional UI to work:
#### Update input fields
Add the `autocomplete` attribute with the value `webauthn` to your input fields. You can add this attribute to multiple input fields, but at least one is required for conditional UI to work.
The `webauthn` value should also be the last entry of the `autocomplete` attribute.
```html
```
#### Preload the passkeys
When your component mounts, you can preload the user's passkeys by calling the `authClient.signIn.passkey` method with the `autoFill` option set to `true`.
To prevent unnecessary calls, we will also add a check to see if the browser supports conditional UI.
```ts
useEffect(() => {
if (!PublicKeyCredential.isConditionalMediationAvailable ||
!PublicKeyCredential.isConditionalMediationAvailable()) {
return;
}
void authClient.signIn.passkey({ autoFill: true })
}, [])
```
Depending on the browser, a prompt will appear to autofill the passkey. If the user has multiple passkeys, they can select the one they want to use.
Some browsers also require the user to first interact with the input field before the autofill prompt appears.
### Debugging
To test your passkey implementation you can use [emulated authenticators](https://developer.chrome.com/docs/devtools/webauthn). This way you can test the registration and sign-in process without even owning a physical device.
## Schema
The plugin require a new table in the database to store passkey data.
Table Name: `passkey`
## Options
**rpID**: A unique identifier for your website. 'localhost' is okay for local dev.
**rpName**: Human-readable title for your website.
**origin**: The URL at which registrations and authentications should occur. '[http://localhost](http://localhost)' and '[http://localhost:PORT](http://localhost:PORT)' are also valid. Do NOT include any trailing /.
file: ./content/docs/plugins/phone-number.mdx
meta: {
"title": "Phone Number",
"description": "Phone number plugin"
}
The phone number plugin extends the authentication system by allowing users to sign in and sign up using their phone number. It includes OTP (One-Time Password) functionality to verify phone numbers.
## Installation
### Add Plugin to the server
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { phoneNumber } from "better-auth/plugins"
const auth = betterAuth({
plugins: [
phoneNumber({ // [!code highlight]
sendOTP: ({ phoneNumber, code }, request) => { // [!code highlight]
// Implement sending OTP code via SMS // [!code highlight]
} // [!code highlight]
}) // [!code highlight]
]
})
```
### Migrate the database
Run the migration or generate the schema to add the necessary fields and tables to the database.
```bash
npx @better-auth/cli migrate
```
```bash
npx @better-auth/cli generate
```
See the [Schema](#schema) section to add the fields manually.
### Add the client plugin
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
import { phoneNumberClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [ // [!code highlight]
phoneNumberClient() // [!code highlight]
] // [!code highlight]
})
```
## Usage
### Send OTP for Verification
To send an OTP to a user's phone number for verification, you can use the `sendVerificationCode` endpoint.
```ts title="auth-client.ts"
await authClient.phoneNumber.sendOtp({
phoneNumber: "+1234567890"
})
```
### Verify Phone Number
After the OTP is sent, users can verify their phone number by providing the code.
```ts title="auth-client.ts"
const isVerified = await authClient.phoneNumber.verify({
phoneNumber: "+1234567890",
code: "123456"
})
```
When the phone number is verified, the `phoneNumberVerified` field in the user table is set to `true`. If `disableSession` is not set to `true`, a session is created for the user. Additionally, if `callbackOnVerification` is provided, it will be called.
### Allow Sign-Up with Phone Number
to allow users to sign up using their phone number, you can pass `signUpOnVerification` option to your plugin configuration. It requires you to pass `getTempEmail` function to generate a temporary email for the user.
```ts title="auth.ts"
export const auth = betterAuth({
plugins: [
phoneNumber({
sendOTP: ({ phoneNumber, code }, request) => {
// Implement sending OTP code via SMS
},
signUpOnVerification: {
getTempEmail: (phoneNumber) => {
return `${phoneNumber}@my-site.com`
},
//optionally, you can also pass `getTempName` function to generate a temporary name for the user
getTempName: (phoneNumber) => {
return phoneNumber //by default, it will use the phone number as the name
}
}
})
]
})
```
### SignIn with Phone number
In addition to signing in a user using send-verify flow, you can also use phone number as an identifier and sign in a user using phone number and password.
```ts
await authClient.signIn.phoneNumber({
phoneNumber: "+123456789",
password: "password",
rememberMe: true //optional defaults to true
})
```
### Update Phone Number
Updating phone number uses the same process as verifying a phone number. The user will receive an OTP code to verify the new phone number.
```ts title="auth-client.ts"
await authClient.phoneNumber.sendOtp({
phoneNumber: "+1234567890" // New phone number
})
```
Then verify the new phone number with the OTP code.
```ts title="auth-client.ts"
const isVerified = await authClient.phoneNumber.verify({
phoneNumber: "+1234567890",
code: "123456",
updatePhoneNumber: true // Set to true to update the phone number
})
```
if user session exist the phone number will be updated automatically
### Disable Session Creation
By default, the plugin creates a session for the user after verifying the phone number. You can disable this behavior by passing `disableSession: true` to the `verify` method.
```ts title="auth-client.ts"
const isVerified = await authClient.phoneNumber.verify({
phoneNumber: "+1234567890",
code: "123456",
disableSession: true
})
```
## Options
* `otpLength`: The length of the OTP code to be generated. Default is `6`.
* `sendOTP`: A function that sends the OTP code to the user's phone number. It takes the phone number and the OTP code as arguments.
* `verifyOTP`: A custom function to verify the OTP code. It takes the phone number and the OTP code as arguments and returns a boolean indicating whether the code is valid.
* `expiresIn`: The time in seconds after which the OTP code expires. Default is `300` seconds.
* `callbackOnVerification`: A function that is called after the phone number is verified. It takes the phone number and the user object as the first argument and a request object as the second argument.
```ts
export const auth = betterAuth({
plugins: [
phoneNumber({
sendOTP: ({ phoneNumber, code }, request) => {
// Implement sending OTP code via SMS
},
callbackOnVerification: async ({ phoneNumber, user }, request) => {
// Implement callback after phone number verification
}
})
]
})
```
* `phoneNumberValidator`: A custom function to validate the phone number. It takes the phone number as an argument and returns a boolean indicating whether the phone number is valid.
* `signUpOnVerification`: An object with the following properties:
* `getTempEmail`: A function that generates a temporary email for the user. It takes the phone number as an argument and returns the temporary email.
* `getTempName`: A function that generates a temporary name for the user. It takes the phone number as an argument and returns the temporary name.
## Schema
The plugin requires 2 fields to be added to the user table
### User Table
file: ./content/docs/plugins/sso.mdx
meta: {
"title": "Single Sign-On (SSO)",
"description": "Integrate Single Sign-On (SSO) with your application."
}
`OIDC` `OAuth2` `SSO`
Single Sign-On (SSO) allows users to authenticate with multiple applications using a single set of credentials. This plugin supports OpenID Connect (OIDC) and OAuth2 providers.
SAML support is coming soon. Upvote the feature request on our [GitHub](https://github.com/better-auth/better-auth/issues/96)
## Installation
### Add Plugin to the server
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { sso } from "better-auth/plugins/sso";
const auth = betterAuth({
plugins: [ // [!code highlight]
sso() // [!code highlight]
] // [!code highlight]
})
```
### Migrate the database
Run the migration or generate the schema to add the necessary fields and tables to the database.
```bash
npx @better-auth/cli migrate
```
```bash
npx @better-auth/cli generate
```
See the [Schema](#schema) section to add the fields manually.
### Add the client plugin
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
import { ssoClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [ // [!code highlight]
ssoClient() // [!code highlight]
] // [!code highlight]
})
```
## Usage
### Register an OIDC Provider
To register an OIDC provider, use the `createOIDCProvider` endpoint and provide the necessary configuration details for the provider.
A redirect URL will be automatically generated using the provider ID. For instance, if the provider ID is `hydra`, the redirect URL would be `{baseURL}/api/auth/sso/hydra`. Note that `/api/auth` may vary depending on your base path configuration.
```ts title="register-provider.ts"
import { authClient } from "@/lib/auth-client";
// only with issuer if the provider supports discovery
await authClient.sso.register({
issuer: "https://idp.example.com",
providerId: "example-provider",
});
// with all fields
await authClient.sso.register({
issuer: "https://idp.example.com
domain: "example.com",
clientId: "client-id",
clientSecret: "client-secret",
authorizationEndpoint: "https://idp.example.com/authorize",
tokenEndpoint: "https://idp.example.com/token",
jwksEndpoint: "https://idp.example.com/jwks",
mapping: {
id: "sub",
email: "email",
emailVerified: "email_verified",
name: "name",
image: "picture",
},
providerId: "example-provider",
});
```
```ts title="register-provider.ts"
const { headers } = await signInWithTestUser();
await auth.api.createOIDCProvider({
body: {
issuer: "https://idp.example.com",
domain: "example.com",
clientId: "your-client-id",
clientSecret: "your-client-secret",
authorizationEndpoint: "https://idp.example.com/authorize",
tokenEndpoint: "https://idp.example.comtoken",
jwksEndpoint: "https://idp.example.com/jwks",
mapping: {
id: "sub",
email: "email",
emailVerified: "email_verified",
name: "name",
image: "picture",
},
providerId: "example-provider",
},
headers,
});
```
### Sign In with SSO
To sign in with an SSO provider, you can call `signIn.sso`
You can sign in using the email with domain matching:
```ts title="sign-in.ts"
const res = await authClient.signIn.sso({
email: "user@example.com",
callbackURL: "/dashboard",
});
```
or you can specify the domain:
```ts title="sign-in-domain.ts"
const res = await authClient.signIn.sso({
domain: "example.com",
callbackURL: "/dashboard",
});
```
You can also sign in using the organization slug if a provider is associated with an organization:
```ts title="sign-in-org.ts"
const res = await authClient.signIn.sso({
organizationSlug: "example-org",
callbackURL: "/dashboard",
});
```
Alternatively, you can sign in using the provider's ID:
```ts title="sign-in-provider-id.ts"
const res = await authClient.signIn.sso({
providerId: "example-provider-id",
callbackURL: "/dashboard",
});
```
To use the server api you can use `signInSSO`
```ts title="sign-in-org.ts"
const res = await auth.api.signInSSO({
body: {
organizationSlug: "example-org",
callbackURL: "/dashboard",
}
});
```
When a user is authenticated, if the user does not exist, the user will be provisioned using the `provisionUser` function. If the organization provisioning is enabled and a provider is associated with an organization, the user will be added to the organization.
```ts title="auth.ts"
const auth = betterAuth({
plugins: [
sso({
provisionUser: async (user) => {
// provision user
},
organizationProvisioning: {
disabled: false,
defaultRole: "member",
getRole: async (user) => {
// get role if needed
},
},
}),
],
});
```
## Schema
The plugin requires additional fields in the `ssoProvider` table to store the provider's configuration.
## Options
### Server
**provisionUser**: A custom function to provision a user when they sign in with an SSO provider.
**organizationProvisioning**: Options for provisioning users to an organization.
file: ./content/docs/plugins/username.mdx
meta: {
"title": "Username",
"description": "Username plugin"
}
The username plugin wraps the email and password authenticator and adds username support. This allows users to sign in and sign up with their username instead of their email.
## Installation
### Add Plugin to the server
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { username } from "better-auth/plugins"
const auth = betterAuth({
plugins: [ // [!code highlight]
username() // [!code highlight]
] // [!code highlight]
})
```
### Migrate the database
Run the migration or generate the schema to add the necessary fields and tables to the database.
```bash
npx @better-auth/cli migrate
```
```bash
npx @better-auth/cli generate
```
See the [Schema](#schema) section to add the fields manually.
### Add the client plugin
```ts title="auth-client.ts"
import { createAuthClient } from "better-auth/client"
import { usernameClient } from "better-auth/client/plugins"
const authClient = createAuthClient({
plugins: [ // [!code highlight]
usernameClient() // [!code highlight]
] // [!code highlight]
})
```
## Usage
### Signup with username
To sign up a user with username, you can use the existing `signUp.email` function provided by the client. The `signUp` function should take a new `username` property in the object.
```ts title="auth-client.ts"
const data = await authClient.signUp.email({
email: "email@domain.com",
name: "Test User",
password: "password1234",
username: "test"
})
```
### Signin with username
To signin a user with username, you can use the `signIn.username` function provided by the client. The `signIn` function takes an object with the following properties:
* `username`: The username of the user.
* `password`: The password of the user.
```ts title="auth-client.ts"
const data = await authClient.signIn.username({
username: "test",
password: "password1234",
})
```
### Update username
To update the username of a user, you can use the `updateUser` function provided by the client.
```ts title="auth-client.ts"
const data = await authClient.updateUser({
username: "new-username"
})
```
## Schema
The plugin requires 1 field to be added to the user table:
## Options
### Min Username Length
The minimum length of the username. Default is `3`.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { username } from "better-auth/plugins"
const auth = betterAuth({
plugins: [
username({
minUsernameLength: 5
})
]
})
```
### Max Username Length
The maximum length of the username. Default is `30`.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { username } from "better-auth/plugins"
const auth = betterAuth({
plugins: [
username({
maxUsernameLength: 100
})
]
})
```
### Username Validator
A function that validates the username. The function should return false if the username is invalid. By default, the username should only contain alphanumeric characters and underscores.
```ts title="auth.ts"
import { betterAuth } from "better-auth"
import { username } from "better-auth/plugins"
const auth = betterAuth({
plugins: [
username({
usernameValidator: (username) => {
if (username === "admin") {
return false
}
}
})
]
})
```
file: ./content/docs/reference/faq.mdx
meta: {
"title": "FAQ",
"description": "Frequently asked questions about Better Auth."
}
This page contains frequently asked questions, common issues, and other helpful information about Better Auth.
When encountering `createAuthClient` related errors, make sure to have the correct import path as it varies based on environment.
If you're using the auth client on react front-end, you'll need to import it from `/react`:
```ts title="component.ts"
import { createAuthClient } from "better-auth/react";
```
Where as if you're using the auth client in Next.js middleware, server-actions, server-components or anything server-related, you'll likely need to import it from `/client`:
```ts title="server.ts"
import { createAuthClient } from "better-auth/client";
```
If you try to call `authClient.getSession` on a server environment (e.g, a Next.js server component), it doesn't work since it can't access the cookies. You can use the `auth.api.getSession` instead and pass the request headers to it.
```tsx title="server.tsx"
import { auth } from "./auth";
import { headers } from "next/headers";
const session = await auth.api.getSession({
headers: await headers()
})
```
if you need to use the auth client on the server for different purposes, you still can pass the request headers to it:
```tsx title="server.tsx"
import { authClient } from "./auth-client";
import { headers } from "next/headers";
const session = await authClient.getSession({
fetchOptions:{
headers: await headers()
}
})
```
Better Auth provides a type-safe way to extend the user and session schemas, take a look at our docs on extending core schema.
Both `useSession` and `getSession` instances are used fundamentally different based on the situation.
`useSession` is a hook, meaning it can trigger re-renders whenever session data changes.
If you have UI you need to change based on user or session data, you can use this hook.
For performance reasons, do not use this hook on your `layout.tsx` file. We
recommend using RSC and use your server auth instance to get the session data
via `auth.api.getSession`.
`getSession` returns a promise containing data and error.
For all other situations where you shouldn't use `useSession`, is when you should be using `getSession`.
`getSession` is available on both server and client auth instances.
Not just the latter.
If you're facing typescript errors, make sure your tsconfig has `strict` set to `true`:
```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,
}
}
```
You can learn more in our Typescript docs.
file: ./content/docs/reference/options.mdx
meta: {
"title": "Options",
"description": "Better Auth configuration options reference."
}
List of all the available options for configuring Better Auth. See [Better Auth Options](https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/types/options.ts#L13).
file: ./content/docs/reference/security.mdx
meta: {
"title": "Security",
"description": "Better Auth security features."
}
This page contains information about security features of Better Auth.
## Password Hashing
Better Auth uses the `scrypt` algorithm to hash passwords by default. This algorithm is designed to be memory-hard and CPU-intensive, making it resistant to brute-force attacks. You can customize the password hashing function by setting the `password` option in the configuration. This option should include a `hash` function to hash passwords and a `verify` function to verify them.
## Session Management
### Session Expiration
Better Auth uses secure session management to protect user data. Sessions are stored in the database or a secondary storage, if configured, to prevent unauthorized access. By default, sessions expire after 7 days, but you can customize this value in the configuration. Additionally, each time a session is used, if it reaches the `updateAge` threshold, the expiration date is extended, which by default is set to 1 day.
### Session Revocation
Better Auth allows you to revoke sessions to enhance security. When a session is revoked, the user is logged out and can no longer access the application. A logged in user can also revoke their own sessions to log out from different devices or browsers.
See the [session management](/docs/concepts/session-management) for more details.
## CSRF Protection
Better Auth ensures CSRF protection by validating the Origin header in requests. This check confirms that requests originate from the application or a trusted source. If a request comes from an untrusted origin, it is blocked to prevent potential CSRF attacks. By default, the origin matching the base URL is trusted, but you can set a list of trusted origins in the trustedOrigins configuration option.
## OAuth State and PKCE
To secure OAuth flows, Better Auth stores the OAuth state and PKCE (Proof Key for Code Exchange) in the database. The state helps prevent CSRF attacks, while PKCE protects against code injection threats. Once the OAuth process completes, these values are removed from the database.
## Cookies
Better Auth assigns secure cookies by default when the base URL uses `https`. These secure cookies are encrypted and only sent over secure connections, adding an extra layer of protection. They are also set with the `sameSite` attribute to `lax` by default to prevent cross-site request forgery attacks. And the `httpOnly` attribute is enabled to prevent client-side JavaScript from accessing the cookie.
For Cross-Subdomain Cookies, you can set the `crossSubDomainCookies` option in the configuration. This option allows cookies to be shared across subdomains, enabling seamless authentication across multiple subdomains.
### Customizing Cookies
You can customize cookie names to minimize the risk of fingerprinting attacks and set specific cookie options as needed for additional control. For more information, refer to the [cookie options](/docs/concepts/cookies).
Plugins can also set custom cookie options to align with specific security needs. If you're using Better Auth in non-browser environments, plugins offer ways to manage cookies securely in those contexts as well.
## Rate Limiting
Better Auth includes built-in rate limiting to safeguard against brute-force attacks. Rate limits are applied across all routes by default, with specific routes subject to stricter limits based on potential risk.
## Trusted Origins
Trusted origins prevent CSRF attacks and block open redirects. You can set a list of trusted origins in the `trustedOrigins` configuration option. Requests from origins not on this list are automatically blocked.
## Reporting Vulnerabilities
If you discover a security vulnerability in Better Auth, please report it to us at [security@better-auth.com](mailto:security@better-auth.com). We address all reports promptly, and credits will be given for validated discoveries.