Files
better-auth/docs/content/docs/concepts/plugins.mdx
2024-10-10 13:55:21 +03:00

397 lines
15 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Plugins
description: Learn how to use plugins with BetterAuth
---
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. Include the plugin in the `plugins` array when creating the client. Most clients require both server and client plugins to work correctly.
```ts title="client.ts"
import { createAuthClient } from "better-auth/client";
const client = createAuthClient({
plugins: [
// Add your client plugins here
]
})
```
## Creating a Plugin
To create a plugin you need to pass an object that satsfies the `BetterAuthPlugin` interface.
The only required property is `id`, which is a unique identifier for the plugin.
```ts title="plugin.ts"
const myPlugin = {
id: "my-plugin",
}
```
### Endpoints
To add endpoints to the server, you can pass `endpoints` that requires an object with the key being any `string` and teh value being `AuthEndpoint`.
To create an Auth Endpoint you'll need to import `createAuthEndpoint` from `better-auth`.
Better Auth uses a library called <Link href="https://github.com/bekacru/better-call"> Better Call </Link> to create endpoints. Better call is a simple web framework made by the same author of Better Auth.
```ts title="plugin.ts"
import { createAuthEndpoint } from "better-auth";
const myPlugin = {
id: "my-plugin",
endpoints: {
getHelloWorld: createAuthEndpoint("/my-plugin/hello-world", {
method: "GET",
}, async(req)=>{
})
}
}
```
Create Auth endpoints wraps around `createEndpoint` from Better Call. Inside the `req` object, it'll provide you with an object called `context` that give you access better-auth specefic 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 defination. It is an object which has the table name as the key and the schema defination 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 defualt 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 peroframnce reasons)
- `intenralAdapter`: this are intenral db calls that are used by Better Auth. You can use this calls for example to create session instead of using `adapter` directly. `intenralAdapter.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 <Link href="https://github.com/bekacru/better-call">Better Call</Link> documentation and the <Link href="https://github.com/better-auth/better-auth/blob/main/packages/better-auth/src/init.ts">source code </Link>.
**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 the endpoint.
- Make sure your paths are unique to avoid conflicts with other plugins.
### 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"
const myPlugin = {
id: "my-plugin",
schema: {
myTable: {
fields: {
name: {
type: "string"
}
}
}
}
}
```
**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`)
`returned`: if the field should be returned when fetching records. (default: `true`)
`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
}
```
### Middleware
You can add middleware to the server by passing a `middleware` array. The middleware array should contain an array of middleware functions created by `createAuthMiddelware`.
`createAuthMiddelware` wraps around `createMiddleware` from Better Call. It takes a function that returns a middleware function. The function will be called with the context object.
```ts title="plugin.ts"
const myPlugin = {
id: "my-plugin",
middleware: [
createAuthMiddelware(async(ctx)=>{
//do something
})
]
}
```
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.
### 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 wnat to do something that affects all requests or responses.
#### On Request
The `onRequest` function is called right before the request is made. It takes two parameters: the `request` and the `context` object.
Heres how it works:
- **Continue as Normal**: If you don't return anything, the request will proceed as usual.
- **Interrupt the Request**: To stop the request and send a response, return an object with a `response` property that contains a `Response` object.
- **Modify the Request**: You can also return a modified `request` object to change the request before it's sent.
```ts title="plugin.ts"
const myPlugin = {
id: "my-plugin",
onRequest: async (requset, context) => {
//do something
},
}
```
#### On Response
The `onResponse` function is executed immediately after a response is returned. It takes two parameters: the `response` and the `context` object.
Heres how to use it:
- **Modify the Response**: You can return a modified response object to change the response before it is sent to the client.
- **Continue Normally**: If you dont return anything, the response will be sent as is.
```ts title="plugin.ts"
const myPlugin = {
id: "my-plugin",
onResponse: async (response, context) => {
//do something
},
}
```
### Hooks
Hooks are similar to middlewares, as well as the `onRequest` and `onResponse` functions. However, unlike `onResponse` and `onRequest`, hooks provide the ability to use a `better-call` handler and include a `matcher` function that lets you target specific requests.
YOu should use hooks when you need to hook into to a specific endpoint or request for more precise control.
```ts title="plugin.ts"
import { createAuthMiddleware } from "better-auth/plugins";
const myPlugin = {
id: "my-plugin",
hooks: {
before: {
[{
matcher: (context)=>{
return context.req.method === "POST"
},
handler: createAuthMiddleware(async(ctx)=>{
//do something before the request
})
}]
},
after: [{
matcher: (context)=>{
return context.req.method === "POST
},
handler: createAuthMiddleware(async(ctx)=>{
//this is the returned response object from the actual endpoint
const returned = ctx.context.returned
//do something after the request
})
}]
}
} satisfies BetterAuthPlugin
```
#### When to use Middelware, Hooks, On Request, On Response
- **Middlewares**: You should use middlewares when you want to use a route matcher to target a group of routes.
- **Hooks**: You should use hooks when you want to target a specific route or request.
- **On Request & On Response**: You should use on request and on response when you want to do something that affects all requests or responses.
### 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 = {
id: "my-plugin",
rateLimit: [
{
pathMatcher: (path)=>{
return path === "/my-plugin/hello-world"
},
limit: 10,
window: 60,
}
]
}
```
### Client
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.
**Endpoint Inference**
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 = {
id: "my-plugin",
$InferServerPlugin: {} as ReturnType<typeof myPlugin>,
} satisfies BetterAuthClientPlugin
```
**getActions**
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 <Link href="https://bette-fetch.vercel.app"> Better fetch </Link> 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 { BetteFetchOptions } from "better-fetch";
const myPluginClient = {
id: "my-plugin",
$InferServerPlugin: {} as ReturnType<typeof myPlugin>,
getActions: ($fetch)=>{
return {
myCustomAction: async (data: {
foo: string,
fetchOptions?: BetteFetchOptions
})=>{
const res = $fetch("/custom/action", {
method: "POST",
body: {
foo: data.foo
}
...data.fetchOptions
})
return res
}
}
}
} satisfies BetterAuthClientPlugin
```
<Callout>
As a general rule, make sure to accept only one argument in the function and make sure you also optionally accept a `fetchOptions` object. This is to make sure the user can pass additional options to the fetch call. And return and object with the `data` and `error` key.
</Callout>
**getAtoms**
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. This atoms should be created using <Link href="https://github.com/nanostores/nanostores">nanostore</Link>. The atoms will be resolved by each framework `useStore` hook provided by nanostore.
```ts title="client-plugin.ts"
import { atom } from "nanostores";
import type { BetterAuthClientPlugin } from "better-auth/client";
const myPluginClient = {
id: "my-plugin",
$InferServerPlugin: {} as ReturnType<typeof myPlugin>,
getAtoms: ($fetch)=>{
const myAtom = atom<null>()
return {
myAtom
}
}
} satisfies BetterAuthClientPlugin
```
See built in plugins for examples of how to use atoms properly.
**pathMethods**
by default, infered 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<typeof myPlugin>,
pathMethods: {
"/my-plugin/hello-world": "POST"
}
} satisfies BetterAuthClientPlugin
```
**fetchPlugins**
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 <Link href="https://better-fetch.vercel.app/docs/plugins">better fetch documentation</Link>.
**atomListners**
This is only useful if you want to provide `hooks` like `useSession` and you want to listen to the atoms and re-evalue the atoms when the atom changes.
You can see how this is used in the built in plugins.