@beignet/productor-auth-better-auth (英语)
运行时间 : Beignet需要Node.js 22.12或更新. Bun是可选的.
[叹息] Beignet是实验性的α软件. 这个
0.0.x包行为早期 评估,且在框架结算时,API可能在发布之间发生变化.
Beignet 应用程序的更好的 Auth 提供者 .
提供者包装已配置 更好的认证
服务器实例并显示共享 AuthPort 从 @beignet/core/ports 打开
ctx.ports.auth。您的应用程序仍然拥有更好的 Auth 配置, 数据库
计划,和认证路线.
概览
这个提供者做什么:
- 用简单而稳定的 API 环绕一个更好的 Auth 实例
- 扩展
ports.auth与getSession, (中文(简体) ),getUser,以及requireUser方法 - 显示自定义应用程序线程的直接端口适配器
- 维护自定义用户类型的类型安全
- 安装 Devtool 时对 devtools 的记录认证检查
此提供者不做的事 :
- 定义数据库计划( 您拥有您的用户表)
- 定义用户类型( 您在应用程序中定义它)
- 配置更好的 Auth( 在您的应用程序中保留机密、 会话策略等)
- 执行登录/签名路径( 直接使用更好的 Auth 路径)
- 管理非洲区域局/任务(即应用逻辑)
安装
bun add @beignet/core @beignet/provider-auth-better-auth [email protected]
提供者支持更好的 Auth >=1.7.7 <1.8.0。标准贝格网
首发针头最好 1.7.14 所以干净的安装使用版本 Beignet
在生成的应用程序中验证 .
以前从更好的 Auth 版本升级时 1.7.7,更新服务器共享
验证存储和所有 OAuth 代理参与者一起。 请求新魔法
链接和重新启动等待 OAuth 或 SAML 签名; 升级前链接和状态
无法完成。 应用程序升级从 1.7.0– 说吧 –1.7.2 还需要账户计划
下文所述的变动。 见
页:1 更好的认证升级指导
用于自定义验证存储更改.
代理技能
这个包装了一种用于编码的 TanStack Intental 技能:
@beignet/provider-auth-better-auth#auth-provider。在接线时装入
更好的 Auth 配置, auth 路由, AuthPort 输入, 推迟的认证端口
注册、 服务器上下文, createAuthHooks,用户/会话助手,
政策、工具认证仪器、测试或认证医生漂移.
设置
1. 在应用程序中配置更好的 Auth
首先,用您的数据库和配置设置更好的 Auth :
// 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. 定义您的端口类型
在您的应用程序中拥有公开会话形状, 然后添加 auth 端口为您的
应用程序的端口类型 :
// 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. 将供应商接入Beignet
在创建您的服务器时注册提供者 :
// 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. 在路由钩上使用认证端口
使用 createAuthHooks(...) 以创建路由范围验证钩:
// 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;
},
});
然后附加连接线路的钩子:
import { defineRouteGroup } from "@/lib/routes";
export const accountRoutes = defineRouteGroup({
name: "account",
hooks: [auth.required()],
routes: [
{
contract: getProfile,
handle: async ({ ctx }) => getProfileUseCase.run({ ctx }),
},
],
});
5. 可选:在使用情况下检查认证
您也可以在使用时检查认证 :
// 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);
});
在标准应用程序形状中,服务器的 context.request 工厂读取
请求一次
ctx.ports.auth.getSession(req) 并存储结果于 ctx.auth选项
然后叫一个应用软件的助手,比如: requireUser(ctx) 而不是依赖
在原始请求.
ctx.ports.users 是一个应用程序属性存储器。 数据库提供者的
ctx.ports.db 条目显示数据库客户端和健康检查;没有
包含特性寄存器.
beignet doctor --strict 检查安装了更好的 Auth 提供者
登记在册 server/providers.ts。供应商不宣布Beignet拥有
env vars 因为更好的 Auth 配置,秘密,数据库适配器选项,
和社交供应商的证明 留在你的应用的更好的Auth设置.
已安装端口
提供者提供 ctx.ports.auth,标准贝格网 AuthPort。 。 。 .
它不会暴露一个更好的 Auth 逃生舱;需要的路由处理器
供应商-本地认证流量应分别挂载更好的Auth自己的路径,
通常在 app/api/auth/[...all]/route.ts。 。 。 .
账户回收和核查
CLI的邮件启动器包括应用自带密码恢复和电子邮件
核查。 已有的带有打入的 Next.js apps getServer().ports.mailer 可以运行
beignet make auth 在安装他们的邮件提供者后。 食谱添加电子邮件
召回电话, /forgot-password, (中文(简体) ), /reset-password, (中文(简体) ), /verify-email账户
测试。 使用 --ui=false 用于无页回调和测试.
lib/auth-settings.ts 控制核查执行(默认:可选),而
lib/auth-email.ts 拥有信件内容、 一个小时过期和会话撤销
重置时。 更好的Auth拥有密码和信使. Beignet 认证提供者
调整应用程序拥有的更好的认证实例 AuthPort。 。 。 .
随着启动器默认,电子邮件更改将一直等待到新地址
经核实。 自定义配置必须启用 user.changeEmail.enabled,供应
sendVerificationEmail,然后离开 updateEmailWithoutVerification 虚假或
省略以使用此流程。 将后者设置为真让未验证的用户
立即改变他们的地址.
保持请求地址可见并使用 authClient.changeEmail没有
sendVerificationEmail,仅核实往来账户地址。 这个
启动设置屏幕处理此流量; 自定义屏幕必须明确连接它 .
在启用行程前配置真正的邮件发送 。 更好的 Auth 继续重置 即使发送失败, 响应也是通用的, 因此监视邮件错误和支持 续发. 内存邮件抓取消息进行测试,而不发送. 自定义认证屏幕和相冲突的配置需要明确的应用程序集成; 跟着 手动恢复设置 财务报告和审定财务报表 核查步骤。 。 。 .
Auth 回调和应用程序端口
更好的 Auth 回调例如 sendResetPassword 和组织
在Beignet外运行的邀请电子邮件请求管道。 到达应用程序
通过启动的服务器发送邮件和日志端口,而不是构建一个
并行邮件客户端 :
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}`,
});
}
这保持了同一仪器上的认证电子邮件, 环境可应用 提供者作为其余的应用程序。 见 认证文件 全文 模式.
开发工具
何时 @beignet/devtools 安装在此提供者之前, 认证检查
出现在仪表板的Auth监视器下.
提供者的记录 auth.getSession, (中文(简体) ), auth.getUser,以及
auth.requireUser 与行动相关的事件、经认证的状态和
持续时间。 不记录用户和会话对象。 提供者失败
记录 .failed 事件名称和原始错误正在重写.
失败行为
未认证 requireUser(...) 电话掷出 AuthUnauthorizedError,哪个
beignet 服务器映射到一个框架所有 401 反应。 更好的认证
会话搜索或路由出错被重新删除, 因此会钩和服务器处理出错
可以一致地报告.
当地测试
使用应用软件的假名 AuthPort 仅限用途的测试和路由测试
需要认证/匿名分支。 保持更好的Auth本身覆盖通过
该应用程序的认证路由测试 和提供者集成测试.
部署说明
在启用前先部署更好的 Auth 数据库计划和秘密配置
认证路线。 在升级更好的 Auth 或更改其插件后,
为您的数据库适配器重新生成计划, 并应用结果
在部署前迁移。 启用的应用程序 twoFactor() 必须包括
插件当前 failedVerificationCount 财务报告和审定财务报表 lockedUntil 字段。 这个
标准 Beignet 启动器无法启用双因子认证, 因此其
默认认证配置不需要这些字段.
将现有应用程序升级到更好的 Auth 1.7
更好的认证 1.7.3 和新的账户 (providerId, accountId)。 。 。 .
新 Beignet 启动程序使用此方案, 但没有 issuer 栏.
如果你从Better Auth安装了计划 1.7.0– 说吧 –1.7.2,让遗产
account.issuer 栏中无效并删除其化合物独有索引
你的图谋和经过审查的迁徙 保留已存在的账户行和发行者
值; 删除列是可选的清理。 SQLite 需要一个表格重建
放松点 NOT NULL在部署更新的运行时间之前应用迁移,
然后核查新的注册和现有的账户注册.
检查复制 (providerId, accountId) 升级前为对。 如与众不同
身份提供者共享提供者的ID,给他们单独的ID并更新
匹配账户行而不合并用户。 跟着Auth走
1.7 升级指南
用于特定数据库步骤和特定提供者的修改.
在特性政策中保留业务授权, 只使用 Beignet 认证钩 hTTP 边界认证.
API 参考
AuthPort<User, Session>
提供者执行导出端口接口
@beignet/core/ports数字 :
getSession(req: Request): Promise<AuthSession<User, Session> | null>
从请求获取当前会话 。 回返 null 如果没有认证.
const session = await ctx.ports.auth.getSession(req);
if (session) {
console.log(session.user);
}
getUser(req: Request): Promise<User | null>
从请求中获取当前用户 。 回返 null 如果没有认证.
这是一种从会话中提取用户的方便方法.
const user = await ctx.ports.auth.getUser(req);
if (user) {
console.log(user.email);
}
requireUser(req: Request): Promise<User>
需要认证用户 。 如果未认证则丢出错误 .
在生命周期钩子中使用此功能或使用需要认证的大小写.
const user = await ctx.ports.auth.requireUser(req);
// user is guaranteed to exist here
掷出 : AuthUnauthorizedError 从 @beignet/core/ports 如果不是的话
认证。 当这个错误到达 Beignet 的服务器运行时间时, 它就是
返回为框架所有 401 以标准错误信封响应.
AuthSession<User, Session>
代表已认证会话 :
interface AuthSession<User = unknown, Session = unknown> {
user: User;
session?: Session;
}
createBetterAuthProvider({ auth })
创建提供者的工厂功能:
function createBetterAuthProvider<User = unknown, Session = unknown>(
options: CreateBetterAuthProviderOptions<User, Session>
): BetterAuthProvider<User, Session>
参数:
options.auth: 在应用程序中配置一个更好的 Auth 服务器实例
回返情况: 可以在服务器上注册的 Beignet 提供者
createBetterAuthPort({ auth, instrumentation? })
创建共享 AuthPort 当应用程序需要自定义提供者设置时直接进行
围绕其更好的实例:
// 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;
定义和登记 appAuthProvider 内 server/providers.ts,在提供者
审计可以确认低级适配器为有效注册,并保留
auth 推迟日期 infra/port-wiring.ts外部生命周期提供者接线,
在适配器时明确通过 Beignet 提供者的仪器化目标
应发出同样的经编辑的认证事件。 适配器不拥有或停止
更好的Auth实例.
高级用途
路由扫描认证
使用 createAuthHooks(...) 从 @beignet/core/server 以强制执行 auth 旁
合同与使用之间的线条:
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 }],
});
自定义错误类型
你可以包起来 requireUser 来扔出自定义错误类型 :
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();
}
};
多重认证战略
当插件覆盖时,通过 Better Auth 配置多个策略
所需的会话、无记名者或 API 键行为。 当应用程序需要认证时
在Better Auth之外,用一个应用软件来编造逻辑 AuthPort 适配器或
HTTP 认证钩. 使用 createBetterAuthPort(...) 当应用程序需要直接
其已有的更好的 Auth 实例的接线 .
类型安全
提供者为您的自定义用户类型维护全部类型安全 :
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"
与更好的 Auth 路径整合
Better Auth为登录,注册等提供自己的路由处理器. 您可以在 Beignet 路线旁挂上这些 :
// 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)
见 更好的认证文档 用于详细了解路线配置.
对于下一个.js 16 应用程序外壳, 一个根 proxy.ts 可以通过检查改进方向
用于在保存前显示更好的 Auth会话 cookie
用户界面路径 :
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*"],
};
这只是个乐观的UX门. 保持 Beignet 的服务器上下文, 认证钩, 策略,并使用大小写助手作为真正的授权边界,因为 只检查 cookie 不验证会话 .
实例
基本设置
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,
});
保护路线
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,
});
可选认证
const listData = async ({ ctx }) => {
const user = ctx.auth?.user;
if (user) {
return {
status: 200,
body: { data: getPersonalizedData(user) },
};
}
return {
status: 200,
body: { data: getPublicData() },
};
};
许可证
麻省理工学院