플러그인

플러그인은 Better Auth의 핵심 요소로, 기본 기능을 확장할 수 있게 해줍니다. 플러그인을 사용하여 새로운 인증 방법이나 기능을 추가하거나 동작을 커스터마이징할 수 있습니다.

Better Auth는 즉시 사용할 수 있는 다양한 내장 플러그인을 제공합니다. 자세한 내용은 플러그인 섹션을 확인하세요. 또한 직접 플러그인을 만들 수도 있습니다.

플러그인 사용하기

플러그인은 서버 측 플러그인, 클라이언트 측 플러그인 또는 둘 다일 수 있습니다.

서버에 플러그인을 추가하려면 auth 설정의 plugins 배열에 포함시키세요. 플러그인은 제공된 옵션으로 초기화됩니다.

server.ts
import { betterAuth } from "better-auth";

export const auth = betterAuth({
    plugins: [
        // 플러그인을 여기에 추가하세요
    ]
});

클라이언트 플러그인은 클라이언트를 생성할 때 추가됩니다. 대부분의 플러그인이 올바르게 작동하려면 서버와 클라이언트 플러그인이 모두 필요합니다. 프론트엔드의 Better Auth auth 클라이언트는 better-auth/client에서 제공하는 createAuthClient 함수를 사용합니다.

auth-client.ts
import { createAuthClient } from "better-auth/client";

const authClient =  createAuthClient({
    plugins: [
        // 클라이언트 플러그인을 여기에 추가하세요
    ]
});

auth-client와 일반 auth 인스턴스를 별도의 파일로 관리하는 것을 권장합니다.

server.ts
auth-client.ts

플러그인 만들기

시작하려면 서버 플러그인이 필요합니다. 서버 플러그인은 모든 플러그인의 핵심이며, 클라이언트 플러그인은 서버 플러그인과 쉽게 작업할 수 있도록 프론트엔드 API 인터페이스를 제공합니다.

서버 플러그인에 클라이언트에서 호출해야 하는 엔드포인트가 있는 경우, 클라이언트 플러그인도 만들어야 합니다.

플러그인이 할 수 있는 것은?

  • 원하는 작업을 수행할 커스텀 endpoint 생성
  • 커스텀 스키마로 데이터베이스 테이블 확장
  • middleware를 사용하여 라우트 매처를 통해 라우트 그룹을 타겟팅하고, 요청을 통해 해당 라우트가 호출될 때만 실행
  • hooks를 사용하여 특정 라우트나 요청을 타겟팅. 엔드포인트가 직접 호출되어도 훅을 실행하려는 경우에 사용
  • 모든 요청 또는 응답에 영향을 주는 작업을 하려면 onRequest 또는 onResponse 사용
  • 커스텀 rate-limit 규칙 생성

서버 플러그인 만들기

서버 플러그인을 만들려면 BetterAuthPlugin 인터페이스를 만족하는 객체를 전달해야 합니다.

유일한 필수 속성은 플러그인의 고유 식별자인 id입니다. 서버와 클라이언트 플러그인 모두 같은 id를 사용할 수 있습니다.

plugin.ts
import type { BetterAuthPlugin } from "better-auth";

export const myPlugin = ()=>{
    return {
        id: "my-plugin",
    } satisfies BetterAuthPlugin
}

플러그인을 함수로 만들 필요는 없지만, 그렇게 하는 것을 권장합니다. 이렇게 하면 플러그인에 옵션을 전달할 수 있고 내장 플러그인과 일관성을 유지할 수 있습니다.

엔드포인트

서버에 엔드포인트를 추가하려면 endpoints를 전달할 수 있습니다. 이는 키가 임의의 문자열이고 값이 AuthEndpoint인 객체가 필요합니다.

Auth Endpoint를 만들려면 better-auth에서 createAuthEndpoint를 가져와야 합니다.

Better Auth는 Better Call이라는 다른 라이브러리를 감싸서 엔드포인트를 생성합니다. Better Call은 Better Auth를 만든 팀에서 만든 간단한 TypeScript 웹 프레임워크입니다.

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는 Better Call의 createEndpoint를 감싸고 있습니다. ctx 객체 내부에서 context라는 또 다른 객체를 제공하며, 이는 options, db, baseURL 등을 포함한 better-auth 전용 컨텍스트에 접근할 수 있게 해줍니다.

컨텍스트 객체

  • appName: 애플리케이션의 이름. 기본값은 "Better Auth"입니다.
  • options: Better Auth 인스턴스에 전달된 옵션.
  • tables: 코어 테이블 정의. 테이블 이름을 키로, 스키마 정의를 값으로 하는 객체입니다.
  • baseURL: auth 서버의 baseURL. 경로를 포함합니다. 예를 들어, 서버가 http://localhost:3000에서 실행 중이면 baseURL은 사용자가 변경하지 않는 한 기본적으로 http://localhost:3000/api/auth입니다.
  • session: 세션 설정. updateAgeexpiresIn 값을 포함합니다.
  • secret: 다양한 목적으로 사용되는 비밀 키. 사용자가 정의합니다.
  • authCookie: 코어 auth 쿠키의 기본 쿠키 설정.
  • logger: Better Auth가 사용하는 로거 인스턴스.
  • db: Better Auth가 데이터베이스와 상호작용하는 데 사용하는 Kysely 인스턴스.
  • adapter: db와 동일하지만 데이터베이스와 상호작용하기 위한 orm 같은 함수를 제공합니다. (원시 SQL 쿼리가 필요하거나 성능상의 이유가 없다면 db보다 이것을 사용하는 것을 권장합니다)
  • internalAdapter: Better Auth가 사용하는 내부 db 호출. 예를 들어, adapter를 직접 사용하는 대신 이 호출을 사용하여 세션을 생성할 수 있습니다. internalAdapter.createSession(userId)
  • createAuthCookie: 쿠키를 set하거나 get하기 위한 쿠키 nameoptions를 가져올 수 있는 헬퍼 함수입니다. 쿠키에 대해 __secure 접두사와 __host 접두사 같은 것을 구현합니다.

다른 속성들에 대해서는 Better Call 문서와 소스 코드를 확인하세요.

엔드포인트 규칙

  • 엔드포인트 경로에 kebab-case를 사용하세요
  • 엔드포인트에는 POST 또는 GET 메서드만 사용하세요.
  • 데이터를 수정하는 모든 함수는 POST 메서드여야 합니다.
  • 데이터를 가져오는 모든 함수는 GET 메서드여야 합니다.
  • API 엔드포인트를 생성할 때는 createAuthEndpoint 함수를 사용하세요.
  • 다른 플러그인과의 충돌을 피하기 위해 경로가 고유한지 확인하세요. 일반적인 경로를 사용하는 경우, 경로에 플러그인 이름을 접두사로 추가하세요. (/hello-world 대신 /my-plugin/hello-world)

스키마

schema 객체를 전달하여 플러그인의 데이터베이스 스키마를 정의할 수 있습니다. 스키마 객체는 테이블 이름을 키로, 스키마 정의를 값으로 가져야 합니다.

plugin.ts
import { BetterAuthPlugin } from "better-auth/plugins";

const myPlugin = ()=> {
    return {
        id: "my-plugin",
        schema: {
            myTable: {
                fields: {
                    name: {
                        type: "string"
                    }
                },
                modelName: "myTable" // 키와 다른 이름을 사용하려는 경우 선택 사항
            }
        }
    } satisfies BetterAuthPlugin
}

필드

기본적으로 better-auth는 각 테이블에 id 필드를 생성합니다. fields 객체에 추가하여 테이블에 추가 필드를 넣을 수 있습니다.

키는 컬럼 이름이고 값은 컬럼 정의입니다. 컬럼 정의는 다음 속성을 가질 수 있습니다:

type: 필드의 타입. string, number, boolean, date일 수 있습니다.

required: 새 레코드에 필드가 필수인지 여부. (기본값: false)

unique: 필드가 고유해야 하는지 여부. (기본값: false)

reference: 필드가 다른 테이블에 대한 참조인지 여부. (기본값: null) 다음 속성을 가진 객체를 받습니다:

  • model: 참조할 테이블 이름.
  • field: 참조할 필드 이름.
  • onDelete: 참조된 레코드가 삭제될 때 취할 액션. (기본값: null)

다른 스키마 속성

disableMigration: 테이블을 마이그레이션하지 않아야 하는지 여부. (기본값: false)

plugin.ts
const myPlugin = (opts: PluginOptions)=>{
    return {
        id: "my-plugin",
        schema: {
            rateLimit: {
                fields: {
                    key: {
                        type: "string",
                    },
                },
                disableMigration: opts.storage.provider !== "database", 
            },
        },
    } satisfies BetterAuthPlugin
}

user 또는 session 테이블에 추가 필드를 추가하면, 타입은 getSessionsignUpEmail 호출에서 자동으로 추론됩니다.

plugin.ts

const myPlugin = ()=>{
    return {
        id: "my-plugin",
        schema: {
            user: {
                fields: {
                    age: {
                        type: "number",
                    },
                },
            },
        },
    } satisfies BetterAuthPlugin
}

이렇게 하면 user 테이블에 age 필드가 추가되고 모든 user 반환 엔드포인트에 age 필드가 포함되며 TypeScript에 의해 적절히 추론됩니다.

user 또는 session 테이블에 민감한 정보를 저장하지 마세요. 민감한 정보를 저장해야 하는 경우 새 테이블을 만드세요.

훅은 클라이언트에서 또는 서버에서 직접 액션이 수행되기 전후에 코드를 실행하는 데 사용됩니다. hooks 객체를 전달하여 서버에 훅을 추가할 수 있으며, 이 객체는 beforeafter 속성을 포함해야 합니다.

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)=>{
                        //요청 전에 무언가를 수행
                        return  {
                            context: ctx // 컨텍스트를 수정하려는 경우
                        }
                    })
                }],
            after: [{
                matcher: (context)=>{
                    return context.path === "/sign-up/email"
                },
                handler: createAuthMiddleware(async (ctx)=>{
                    return ctx.json({
                        message: "Hello World"
                    }) // 응답을 수정하려는 경우
                })
            }]
        }
    } satisfies BetterAuthPlugin
}

미들웨어

middlewares 배열을 전달하여 서버에 미들웨어를 추가할 수 있습니다. 이 배열은 각각 pathmiddleware 속성을 가진 미들웨어 객체를 포함해야 합니다. 훅과 달리 미들웨어는 클라이언트로부터의 api 요청에서만 실행됩니다. 엔드포인트가 직접 호출되면 미들웨어는 실행되지 않습니다.

path는 문자열이거나 경로 매처일 수 있으며, better-call과 동일한 경로 매칭 시스템을 사용합니다.

미들웨어에서 APIError를 throw하거나 Response 객체를 반환하면 요청이 중지되고 응답이 클라이언트로 전송됩니다.

plugin.ts
const myPlugin = ()=>{
    return {
        id: "my-plugin",
        middlewares: [
            {
                path: "/my-plugin/hello-world",
                middleware: createAuthMiddleware(async(ctx)=>{
                    //무언가를 수행
                })
            }
        ]
    } satisfies BetterAuthPlugin
}

On Request & On Response

미들웨어 외에도 요청이 이루어지기 직전과 응답이 반환된 직후에 연결할 수 있습니다. 이는 모든 요청이나 응답에 영향을 주는 작업을 하려는 경우에 주로 유용합니다.

On Request

onRequest 함수는 요청이 이루어지기 직전에 호출됩니다. 두 개의 매개변수를 받습니다: requestcontext 객체.

작동 방식:

  • 정상적으로 계속: 아무것도 반환하지 않으면 요청이 평소대로 진행됩니다.
  • 요청 중단: 요청을 중지하고 응답을 보내려면 Response 객체를 포함하는 response 속성이 있는 객체를 반환합니다.
  • 요청 수정: 수정된 request 객체를 반환하여 요청이 전송되기 전에 변경할 수도 있습니다.
plugin.ts
const myPlugin = ()=> {
    return  {
        id: "my-plugin",
        onRequest: async (request, context) => {
            //무언가를 수행
        },
    } satisfies BetterAuthPlugin
}

On Response

onResponse 함수는 응답이 반환된 직후에 실행됩니다. 두 개의 매개변수를 받습니다: responsecontext 객체.

사용 방법:

  • 응답 수정: 수정된 응답 객체를 반환하여 클라이언트로 전송되기 전에 응답을 변경할 수 있습니다.
  • 정상적으로 계속: 아무것도 반환하지 않으면 응답이 있는 그대로 전송됩니다.
plugin.ts
const myPlugin = ()=>{
    return {
        id: "my-plugin",
        onResponse: async (response, context) => {
            //무언가를 수행
        },
    } satisfies BetterAuthPlugin
}

Rate Limit

rateLimit 배열을 전달하여 플러그인에 대한 커스텀 rate limit 규칙을 정의할 수 있습니다. rate limit 배열은 rate limit 객체의 배열을 포함해야 합니다.

plugin.ts
const myPlugin = ()=>{
    return {
        id: "my-plugin",
        rateLimit: [
            {
                pathMatcher: (path)=>{
                    return path === "/my-plugin/hello-world"
                },
                limit: 10,
                window: 60,
            }
        ]
    } satisfies BetterAuthPlugin
}

서버 플러그인 헬퍼 함수

서버 플러그인을 만들기 위한 몇 가지 추가 헬퍼 함수가 있습니다.

getSessionFromCtx

auth 미들웨어의 context를 전달하여 클라이언트의 세션 데이터를 가져올 수 있습니다.

plugin.ts
import {  createAuthMiddleware } from "better-auth/plugins";
import { getSessionFromCtx } from "better-auth/api";

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);
                    //클라이언트의 세션으로 무언가를 수행합니다.

                    return  {
                        context: ctx
                    }
                })
            }],
    }
} satisfies BetterAuthPlugin

sessionMiddleware

클라이언트가 유효한 세션을 가지고 있는지 확인하는 미들웨어입니다. 클라이언트가 유효한 세션을 가지고 있으면 세션 데이터를 컨텍스트 객체에 추가합니다.

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], 
            }, async(ctx) => {
                const session = ctx.context.session;
                return ctx.json({
                    message: "Hello World"
                })
            })
        }
    } satisfies BetterAuthPlugin
}

클라이언트 플러그인 만들기

엔드포인트가 클라이언트에서 호출되어야 하는 경우, 클라이언트 플러그인도 만들어야 합니다. Better Auth 클라이언트는 서버 플러그인에서 엔드포인트를 추론할 수 있습니다. 추가적인 클라이언트 측 로직도 추가할 수 있습니다.

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

export const myPluginClient = ()=>{
    return {
        id: "my-plugin",
    } satisfies BetterAuthClientPlugin
}

엔드포인트 인터페이스

엔드포인트는 클라이언트 플러그인에 $InferServerPlugin 키를 추가하여 서버 플러그인에서 추론됩니다.

클라이언트는 path를 객체로 추론하고 kebab-case를 camelCase로 변환합니다. 예를 들어 /my-plugin/hello-worldmyPlugin.helloWorld가 됩니다.

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

const myPluginClient = ()=> {
    return  {
        id: "my-plugin",
        $InferServerPlugin: {} as ReturnType<typeof myPlugin>,
    } satisfies BetterAuthClientPlugin
}

Get actions

클라이언트에 추가 메서드 등을 추가해야 하는 경우 getActions 함수를 사용할 수 있습니다. 이 함수는 클라이언트의 fetch 함수와 함께 호출됩니다.

Better Auth는 Better fetch 를 사용하여 요청을 만듭니다. Better fetch는 Better Auth의 저자가 만든 간단한 fetch 래퍼입니다.

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<typeof myPlugin>,
    getActions: ($fetch)=>{
        return {
            myCustomAction: async (data: {
                foo: string,
            }, fetchOptions?: BetterFetchOption)=>{
                const res = $fetch("/custom/action", {
                    method: "POST",
                    body: {
                        foo: data.foo
                    },
                    ...fetchOptions
                })
                return res
            }
        }
    }
} satisfies BetterAuthClientPlugin

일반적인 지침으로, 각 함수가 하나의 인자만 받고, 사용자가 fetch 호출에 추가 옵션을 전달할 수 있도록 fetchOptions에 대한 선택적 두 번째 인자를 받도록 하세요. 함수는 data와 error 키를 포함하는 객체를 반환해야 합니다.

사용 사례가 API 호출을 넘어서는 액션을 포함하는 경우, 이 규칙에서 벗어나도 됩니다.

Get Atoms

이것은 useSession 같은 hooks를 제공하려는 경우에만 유용합니다.

Get atoms는 better fetch의 fetch 함수와 함께 호출되며 atoms를 포함하는 객체를 반환해야 합니다. atoms는 nanostores를 사용하여 생성되어야 합니다. atoms는 nanostores에서 제공하는 각 프레임워크의 useStore 훅에 의해 해결됩니다.

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

atoms를 제대로 사용하는 방법에 대한 예제는 내장 플러그인을 참조하세요.

Path methods

기본적으로 추론된 경로는 body가 필요하지 않으면 GET 메서드를 사용하고, body가 필요하면 POST를 사용합니다. pathMethods 객체를 전달하여 이를 재정의할 수 있습니다. 키는 경로여야 하고 값은 메서드("POST" | "GET")여야 합니다.

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

Fetch plugins

better fetch 플러그인을 사용해야 하는 경우 fetchPlugins 배열에 전달할 수 있습니다. better fetch 플러그인에 대한 자세한 내용은 better fetch 문서를 참조하세요.

Atom Listeners

이것은 useSession 같은 hooks를 제공하려는 경우에만 유용하며, atoms를 수신하고 변경될 때 다시 평가하려는 경우입니다.

내장 플러그인에서 이것이 어떻게 사용되는지 확인할 수 있습니다.