@beignet/provider-auth-better-auth

中文文档
0.0.65发布于 2026/11/08MITTypeScriptES Module零依赖近 30 天下载 1864

Better Auth provider for Beignet - adds auth port for authentication and session management

原始使用文档
文档目录33 节
  1. Overview
  2. Install
  3. Agent skills
  4. Setup
  5. 1. Configure Better Auth in your app
  6. 2. Define your ports type
  7. 3. Wire the provider into Beignet
  8. 4. Use the auth port in route hooks
  9. 5. Optional: check auth in use cases
  10. Installed ports
  11. Account recovery and verification
  12. Auth callbacks and app ports
  13. Devtools
  14. Failure behavior
  15. Local and tests
  16. Deployment notes
  17. Upgrading an existing app to Better Auth 1.7
  18. API reference
  19. AuthPort<User, Session>
  20. AuthSession<User, Session>
  21. createBetterAuthProvider({ auth })
  22. createBetterAuthPort({ auth, instrumentation? })
  23. Advanced usage
  24. Route-scoped authentication
  25. Custom error types
  26. Multiple authentication strategies
  27. Type safety
  28. Integration with Better Auth routes
  29. Examples
  30. Basic setup
  31. Protecting routes
  32. Optional authentication
  33. License

@beignet/provider-auth-better-auth

Runtime: Beignet requires Node.js 22.12 or newer. Bun is optional.

[!CAUTION] Beignet is experimental alpha software. The 0.0.x package line is for early evaluation, and APIs may change between releases while the framework settles.

Better Auth provider for Beignet applications.

The provider wraps an already-configured Better Auth server instance and exposes the shared AuthPort from @beignet/core/ports on ctx.ports.auth. Your app still owns Better Auth configuration, database schema, and auth routes.

Overview

What this provider does:

  • Wraps a Better Auth instance with a simple, stable API
  • Extends ports.auth with getSession, getUser, and requireUser methods
  • Exposes a direct port adapter for custom app wiring
  • Maintains type safety for your custom User type
  • Records auth checks in devtools when devtools is installed

What this provider does NOT do:

  • Define database schema (you own your user table)
  • Define the User type (you define it in your app)
  • Configure Better Auth (secrets, session strategy, etc. stay in your app)
  • Implement login/signup routes (use Better Auth's routes directly)
  • Manage RBAC/permissions (that's application logic)

Install

bun add @beignet/core @beignet/provider-auth-better-auth [email protected]

The provider supports Better Auth >=1.7.7 <1.8.0. The standard Beignet starter pins Better Auth to 1.7.14 so clean installs use the version Beignet validates in generated apps.

When upgrading from Better Auth versions before 1.7.7, update servers sharing verification storage and all OAuth Proxy participants together. Request new Magic Links and restart pending OAuth or SAML sign-ins; pre-upgrade links and state cannot complete. Apps upgrading from 1.7.0–1.7.2 also need the account schema change described below. See the Better Auth upgrade guidance for custom verification storage changes.

Agent skills

This package ships a TanStack Intent skill for coding agents: @beignet/provider-auth-better-auth#auth-provider. Load it when wiring Better Auth configuration, auth routes, AuthPort typing, deferred auth port registration, server context, createAuthHooks, user/session helpers, policies, devtools auth instrumentation, tests, or auth doctor drift.

Setup

1. Configure Better Auth in your app

First, set up Better Auth with your database and configuration:

// lib/better-auth.ts
import { betterAuth } from "better-auth";
import { db } from "./db"; // Your Drizzle/Prisma/etc. client

export const auth = betterAuth({
  database: db,
  emailAndPassword: {
    enabled: true,
  },
  // ...other Better Auth configuration
});

2. Define your ports type

Own the public session shape in your app, then add the auth port to your application's ports type:

// ports/auth.ts
import type {
  AuthPort as BeignetAuthPort,
  AuthSession as BeignetAuthSession,
} from "@beignet/core/ports";

export type AuthUser = {
  id: string;
  name?: string | null;
  email?: string | null;
  image?: string | null;
};

export type AuthSessionMetadata = unknown;

export type AuthPort = BeignetAuthPort<AuthUser, AuthSessionMetadata>;
export type AuthSession = BeignetAuthSession<AuthUser, AuthSessionMetadata>;
// ports/index.ts
import type { ActivityActor, GatePort } from "@beignet/core/ports";
import type { UserRepository } from "@/features/users/ports";
import type { AuthPort } from "./auth";

export type AppAuthorizationContext = {
  actor: ActivityActor;
};

export type AppPorts = {
  auth: AuthPort;
  gate: GatePort<AppAuthorizationContext, []>;
  users: UserRepository;
  // ...other ports (db, mailer, eventBus, etc.)
};

3. Wire the provider into Beignet

Register the provider when creating your server:

// server/providers.ts
import { createBetterAuthProvider } from "@beignet/provider-auth-better-auth";
import { auth } from "@/lib/better-auth";

export const providers = [
  createBetterAuthProvider({ auth }),
  // ...other providers
];
// app-context.ts
import type { ActivityActor, BoundGate } from "@beignet/core/ports";
import type { InferProviderPorts } from "@beignet/core/providers";
import type { TraceContext } from "@beignet/core/tracing";
import type { AppPorts } from "@/ports";
import type { AuthSession } from "@/ports/auth";
import type { providers } from "@/server/providers";

export type AppRuntimePorts = AppPorts & InferProviderPorts<typeof providers>;

export type AppContext = {
  actor: ActivityActor;
  auth: AuthSession | null;
  gate: BoundGate<[]>;
  requestId: string;
  ports: AppRuntimePorts;
} & Partial<TraceContext>;
// infra/port-wiring.ts
import { createGate, definePorts } from "@beignet/core/ports";
import type { AppPorts } from "@/ports";

const gate = createGate({ policies: [] });

export const initialPorts = definePorts<AppPorts>()({
  bound: { gate },
  deferred: ["auth"],
});
// server/context.ts
import { createAnonymousActor, createUserActor } from "@beignet/core/ports";
import { defineServerContext } from "@beignet/core/server";
import type { AppContext, AppRuntimePorts } from "@/app-context";

export const appContext = defineServerContext<AppContext, AppRuntimePorts>()({
  gate: (ports) => ports.gate,
  request: async ({ req, ports, requestId, trace }) => {
    const auth = await ports.auth.getSession(req);

    return {
      actor: auth ? createUserActor(auth.user.id) : createAnonymousActor(),
      auth,
      requestId,
      ...trace,
      ports,
    };
  },
});
// server/index.ts
import { createNextServer, createNextServerLoader } from "@beignet/next";
import { initialPorts } from "@/infra/port-wiring";
import { appContext } from "@/server/context";
import { routes } from "@/server/routes";

export const getServer = createNextServerLoader(async () => {
  const { providers } = await import("./providers");

  return createNextServer({
    ports: initialPorts,
    providers,
    context: appContext,
    routes,
  });
});

4. Use the auth port in route hooks

Use createAuthHooks(...) to create route-scoped auth hooks:

// server/auth-hooks.ts
import { createAuthHooks } from "@beignet/core/server";

export const auth = createAuthHooks<AppContext>()({
  resolve: ({ ctx }) => {
    return ctx.auth ? { user: ctx.auth.user } : null;
  },
});

Then attach the hooks where routes are wired:

import { defineRouteGroup } from "@/lib/routes";

export const accountRoutes = defineRouteGroup({
  name: "account",
  hooks: [auth.required()],
  routes: [
    {
      contract: getProfile,
      handle: async ({ ctx }) => getProfileUseCase.run({ ctx }),
    },
  ],
});

5. Optional: check auth in use cases

You can also check authentication in use cases:

// features/users/use-cases/get-profile.ts
import { z } from "zod";
import { requireUser } from "@/lib/auth";
import { useCase } from "@/lib/use-case";

const UserProfileSchema = z.object({
  id: z.string(),
  email: z.string().email(),
});

export const getUserProfile = useCase
  .query("users.profile")
  .input(z.object({ userId: z.string() }))
  .output(UserProfileSchema)
  .run(async ({ ctx, input }) => {
    requireUser(ctx);

    return ctx.ports.users.getProfile(input.userId);
  });

In the standard app shape, the server's context.request factory reads the request once with ctx.ports.auth.getSession(req) and stores the result on ctx.auth. Use cases then call an app-owned helper such as requireUser(ctx) instead of depending on the raw request.

ctx.ports.users is an app-owned feature repository. The database provider's ctx.ports.db entry exposes the database client and health check; it does not contain feature repositories.

beignet doctor --strict checks that installed Better Auth providers are registered in server/providers.ts. The provider does not declare Beignet-owned env vars because Better Auth configuration, secrets, database adapter options, and social provider credentials stay in your app's Better Auth setup.

Installed ports

The provider contributes ctx.ports.auth, the standard Beignet AuthPort. It does not expose a Better Auth escape hatch; route handlers that need provider-native auth flows should mount Better Auth's own routes separately, usually under app/api/auth/[...all]/route.ts.

Account recovery and verification

The CLI's mail-enabled starter includes app-owned password recovery and email verification. Existing Next.js apps with a typed getServer().ports.mailer can run beignet make auth after installing their mail provider. The recipe adds email callbacks, /forgot-password, /reset-password, /verify-email, and account tests. Use --ui=false for callbacks and tests without pages.

lib/auth-settings.ts controls verification enforcement (default: optional), while lib/auth-email.ts owns message content, one-hour expiry, and session revocation on reset. Better Auth owns passwords and tokens. The Beignet auth provider adapts the app-owned Better Auth instance to AuthPort.

With the starter defaults, email changes stay pending until the new address is verified. Custom configurations must enable user.changeEmail.enabled, supply sendVerificationEmail, and leave updateEmailWithoutVerification false or omitted to use this flow. Setting the latter to true lets an unverified user change their address immediately.

Keep the requested address visible and resend using authClient.changeEmail, not sendVerificationEmail, which only verifies the current account address. The starter settings screen handles this flow; custom screens must wire it explicitly.

Configure real mail delivery before enabling the journey. Better Auth keeps reset responses generic even on delivery failures, so monitor mail errors and support resending. Memory mail captures messages for tests and does not deliver them. Custom auth screens and conflicting configuration need explicit app integration; follow the manual recovery setup and verification steps.

Auth callbacks and app ports

Better Auth callbacks such as sendResetPassword and organization invitation emails run outside the Beignet request pipeline. Reach the app's mail and logger ports through the booted server instead of constructing a parallel mail client:

async sendResetPassword({ user, url }) {
  // Dynamic import breaks the module cycle (server -> providers -> auth);
  // getServer() is memoized, so this resolves to a cached instance.
  const { getServer } = await import("@/server");
  const { ports } = await getServer();
  await ports.mailer.send({
    to: user.email,
    subject: "Reset your password",
    text: `Reset your password: ${url}`,
  });
}

This keeps auth email on the same instrumented, environment-swappable provider as the rest of the app. See the Authentication docs for the full pattern.

Devtools

When @beignet/devtools is installed before this provider, auth checks appear under the dashboard's Auth watcher.

The provider records auth.getSession, auth.getUser, and auth.requireUser events with the operation, authenticated status, and duration. User and session objects are not recorded. Provider failures are recorded with .failed event names and the original error is rethrown.

Failure behavior

Unauthenticated requireUser(...) calls throw AuthUnauthorizedError, which the Beignet server maps to a framework-owned 401 response. Better Auth session lookup or route errors are rethrown so hooks and server error handling can report them consistently.

Local and tests

Use an app-owned fake AuthPort in use-case tests and route tests that only need authenticated/anonymous branches. Keep Better Auth itself covered through the app's auth route tests and provider integration tests.

Deployment notes

Deploy the Better Auth database schema and secret configuration before enabling authenticated routes. After upgrading Better Auth or changing its plugins, regenerate the schema for your database adapter and apply the resulting migration before deployment. Apps that enable twoFactor() must include the plugin's current failedVerificationCount and lockedUntil fields. The standard Beignet starter does not enable two-factor authentication, so its default auth configuration does not require those fields.

Upgrading an existing app to Better Auth 1.7

Better Auth 1.7.3 and newer identify accounts by (providerId, accountId). New Beignet starters use this schema without an issuer column.

If you installed the schema from Better Auth 1.7.0–1.7.2, make the legacy account.issuer column nullable and remove its compound unique index in both your schema and a reviewed migration. Preserve existing account rows and issuer values; removing the column is optional cleanup. SQLite needs a table rebuild to relax NOT NULL. Apply the migration before deploying the updated runtime, then verify new sign-ups and existing-account sign-ins.

Check for duplicate (providerId, accountId) pairs before upgrading. If distinct identity providers share a provider ID, give them separate IDs and update the matching account rows without merging users. Follow Better Auth's 1.7 upgrade guide for database-specific steps and provider-specific changes.

Keep business authorization in feature policies and use Beignet auth hooks only for HTTP-boundary authentication.

API reference

AuthPort<User, Session>

The provider implements the auth port interface exported by @beignet/core/ports:

getSession(req: Request): Promise<AuthSession<User, Session> | null>

Get the current session from a Request. Returns null if not authenticated.

const session = await ctx.ports.auth.getSession(req);
if (session) {
  console.log(session.user);
}

getUser(req: Request): Promise<User | null>

Get the current user from a Request. Returns null if not authenticated.

This is a convenience method that extracts the user from the session.

const user = await ctx.ports.auth.getUser(req);
if (user) {
  console.log(user.email);
}

requireUser(req: Request): Promise<User>

Require an authenticated user. Throws an error if not authenticated.

Use this in lifecycle hooks or use cases that require authentication.

const user = await ctx.ports.auth.requireUser(req);
// user is guaranteed to exist here

Throws: AuthUnauthorizedError from @beignet/core/ports if not authenticated. When this error reaches Beignet's server runtime, it is returned as a framework-owned 401 response with the standard error envelope.

AuthSession<User, Session>

Represents an authenticated session:

interface AuthSession<User = unknown, Session = unknown> {
  user: User;
  session?: Session;
}

createBetterAuthProvider({ auth })

Factory function that creates the provider:

function createBetterAuthProvider<User = unknown, Session = unknown>(
  options: CreateBetterAuthProviderOptions<User, Session>
): BetterAuthProvider<User, Session>

Parameters:

  • options.auth: A Better Auth server instance configured in your application

Returns: A Beignet provider that can be registered with the server

createBetterAuthPort({ auth, instrumentation? })

Create the shared AuthPort directly when an app needs custom provider setup around its Better Auth instance:

// server/providers.ts
import { createProvider } from "@beignet/core/providers";
import { createBetterAuthPort } from "@beignet/provider-auth-better-auth";
import { auth } from "@/lib/better-auth";

export const appAuthProvider = createProvider({
  name: "app-auth",
  setup({ ports }) {
    return {
      ports: {
        auth: createBetterAuthPort({ auth, instrumentation: ports }),
      },
    };
  },
});

export const providers = [appAuthProvider] as const;

Define and register appAuthProvider in server/providers.ts, where provider audit can recognize the lower-level adapter as valid registration, and keep auth deferred in infra/port-wiring.ts. Outside lifecycle provider wiring, pass a Beignet provider instrumentation target explicitly when the adapter should emit the same redacted auth events. The adapter does not own or stop the Better Auth instance.

Advanced usage

Route-scoped authentication

Use createAuthHooks(...) from @beignet/core/server to enforce auth beside the contract-to-use-case wiring:

import { defineRouteGroup } from "@/lib/routes";

const users = defineContractGroup();

const getProfile = users
  .get("/api/profile")
  .responses({
    200: z.object({ name: z.string() }),
  })
  .meta({ auth: "required" });

const auth = createAuthHooks<AppContext>()({
  resolve: ({ ctx }) => {
    return ctx.auth ? { user: ctx.auth.user } : null;
  },
});

export const userRoutes = defineRouteGroup({
  name: "users",
  hooks: [auth.required()],
  routes: [{ contract: getProfile, handle: getProfileHandler }],
});

Custom error types

You can wrap requireUser to throw custom error types:

class UnauthorizedError extends Error {
  constructor() {
    super("Unauthorized");
    this.name = "UnauthorizedError";
  }
}

export const requireAuth = async (
  req: Request,
  auth: { requireUser: (req: Request) => Promise<unknown> },
) => {
  try {
    return await auth.requireUser(req);
  } catch (error) {
    throw new UnauthorizedError();
  }
};

Multiple authentication strategies

Configure multiple strategies through Better Auth when its plugins cover the required session, bearer, or API-key behavior. When an app needs authentication outside Better Auth, compose that logic in an app-owned AuthPort adapter or HTTP auth hook. Use createBetterAuthPort(...) when the app needs direct wiring for its existing Better Auth instance.

Type safety

The provider maintains full type safety for your custom User type:

type MyUser = {
  id: string;
  email: string;
  role: "admin" | "user";
};

const authProvider = createBetterAuthProvider<MyUser>({ auth });

// Later, in your routes:
const user = await ctx.ports.auth.requireUser(req);
// user.role is typed as "admin" | "user"

Integration with Better Auth routes

Better Auth provides its own route handlers for login, signup, etc. You can mount these alongside your Beignet routes:

// app/api/auth/[...all]/route.ts
import { toNextJsHandler } from "better-auth/next-js";
import { auth } from "@/lib/better-auth";

// Better Auth handles /api/auth/*
export const { GET, POST } = toNextJsHandler(auth);

// Your Beignet routes handle /api/app/*
// (mounted separately)

See the Better Auth documentation for details on route configuration.

For Next.js 16 app shells, a root proxy.ts can improve redirects by checking for the presence of the Better Auth session cookie before rendering protected UI routes:

import { getSessionCookie } from "better-auth/cookies";
import { NextResponse, type NextRequest } from "next/server";

export function proxy(request: NextRequest) {
  const sessionCookie = getSessionCookie(request);

  if (!sessionCookie) {
    return NextResponse.redirect(new URL("/sign-in", request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/dashboard/:path*", "/settings/:path*"],
};

This is only an optimistic UX gate. Keep Beignet's server context, auth hooks, policies, and use-case helpers as the real authorization boundary, because a cookie-only check does not validate the session.

Examples

Basic setup

import { betterAuth } from "better-auth";
import { createNextServer } from "@beignet/next";
import { createBetterAuthProvider } from "@beignet/provider-auth-better-auth";
import { initialPorts } from "@/infra/port-wiring";
import { appContext } from "@/server/context";
import { routes } from "@/server/routes";

const auth = betterAuth({ database: db });

const server = await createNextServer({
  ports: initialPorts,
  providers: [createBetterAuthProvider({ auth })],
  context: appContext,
  routes,
});

Protecting routes

import { AuthUnauthorizedError } from "@beignet/core/ports";

const authHook = {
  name: "auth",
  beforeHandle: async ({ ctx }) => {
    if (!ctx.auth) {
      throw new AuthUnauthorizedError();
    }

    return { ctx: { ...ctx, user: ctx.auth.user } };
  },
};

const server = await createNextServer({
  ports: initialPorts,
  providers: [createBetterAuthProvider({ auth })],
  hooks: [authHook],
  context: appContext,
  routes,
});

Optional authentication

const listData = async ({ ctx }) => {
  const user = ctx.auth?.user;

  if (user) {
    return {
      status: 200,
      body: { data: getPersonalizedData(user) },
    };
  }

  return {
    status: 200,
    body: { data: getPublicData() },
  };
};

License

MIT

版本与发布记录

@beignet/provider-auth-better-auth 共发布 65 个版本,首次发布于 2026/05/27;过去 12 个月发布了 65 个版本。

版本发布日期距今
0.0.65latest2026/11/08今天
0.0.642026/11/034 天前
0.0.632026/11/026 天前
0.0.622026/11/017 天前
0.0.612026/11/017 天前
0.0.602026/11/017 天前

关于 @beignet/provider-auth-better-auth 的常见问题

@beignet/provider-auth-better-auth 是什么?

Better Auth provider for Beignet - adds auth port for authentication and session management

如何安装 @beignet/provider-auth-better-auth?

在项目目录运行 npm install @beignet/provider-auth-better-auth,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:better-auth(>=1.7.7 <1.8.0)、@beignet/core(>=0.0.3 <1.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@beignet/provider-auth-better-auth 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@beignet/provider-auth-better-auth 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@beignet/provider-auth-better-auth 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.12.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@beignet/provider-auth-better-auth 可以商用吗?

@beignet/provider-auth-better-auth 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@beignet/provider-auth-better-auth 的下载量有多少?

根据 npm 官方下载统计,@beignet/provider-auth-better-auth 在 2026/10/08 – 2026/11/06 这 30 天内被下载 1,864 次,日均约 62 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@beignet/provider-auth-better-auth 还在维护吗?

最新版本 0.0.65 发布于 2026/11/08(今天);至今共发布 65 个版本,其中过去 12 个月发布了 65 个;首次发布于 2026/05/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。