@authlocker/ 更好 - auth
更好的认证插件 奥特洛克电子邮件和短信 由 AuthLocker 生成并检查的一次性代码, 并带有更好的 Auth 为您创建会话 .
pnpm add @authlocker/better-auth better-auth
要求数 better-auth 1.7.0或更新大量更好的认证材料
互联网上写着1.6或更早
下文在1.7中有所改变——它们被称作重要之处.
它做什么,它故意没有
三个端点,安装在您已有的更好的 Auth 处理器内:
| 终点 | 客户端呼叫 | 对 |
|---|---|---|
POST /api/auth/authlocker/send-otp | authClient.authlocker.sendOtp() | 请 AuthLocker 生成并发送代码 |
POST /api/auth/authlocker/verify-otp | authClient.authlocker.verifyOtp() | 检查、找到或创建用户、设置会话 cookie |
POST /api/auth/authlocker/resend-otp | authClient.authlocker.resendOtp() | 再次发送相同的验证 |
AuthLocker 生成并检查每个代码. 这个插件从不薄荷,
永远不要存储一个, 从来没有计算尝试。 更好的奥特 verification
表格完全没有使用——重放保护,五通关卡
10分钟后, 三档预算和第六十二档
都住在AuthLocker,按键 verificationId。 。 。 .
如果你打开线人 期待检察官办公室的正常账簿记录, 因此,应当明确指出:这里没有任何人。 这也是重点所在。 满 授权就是让Twilio验证欺诈的分数 节流阀和短消息 花帽在玩。 仅限插件 已交付 消息会把一切都扔掉的.
唯一不住在奥特洛克的地方 是限制你的速度 应用程序将调用它, 因此插件宣布它自己的费率限制为防御 深度:每分5发,每分10次验证,每个客户端.
设置
1. 联合国. lib/auth.ts
import { betterAuth } from "better-auth";
import { nextCookies } from "better-auth/next-js";
import { authlocker } from "@authlocker/better-auth";
export const auth = betterAuth({
database: /* your adapter */,
plugins: [
authlocker({
clientId: process.env.AUTHLOCKER_CLIENT_ID!,
clientSecret: process.env.AUTHLOCKER_CLIENT_SECRET!,
}),
// `nextCookies()` must be LAST. It works by hooking every response after
// the other plugins have written their cookies; anything registered after
// it sets cookies that never reach the browser.
nextCookies(),
],
});
客户端的秘密永远留在服务器上. POST /api/v1/verify/*
带有 HTTP Basic 和 AuthLocker 的认证 发送任何 CORS 头, 所以 a
浏览器无法调用它, 即使你愿意将这个秘密传送给一个.
这个限制是这个插件存在的原因 .
从 POST /oauth/register,或 npx botparty authlocker provision。 。 。 .
2. 联合国. app/api/auth/[...all]/route.ts
import { toNextJsHandler } from "better-auth/next-js";
import { auth } from "@/lib/auth";
export const { GET, POST } = toNextJsHandler(auth.handler);
3个. lib/auth-client.ts
import { createAuthClient } from "better-auth/react";
import { authlockerClient } from "@authlocker/better-auth/client";
export const authClient = createAuthClient({
plugins: [authlockerClient()],
});
方法名称来源于端点路径 kebab- case 到 caham- case, 所以
/authlocker/send-otp 变成 authClient.authlocker.sendOtp()您没有
宣布他们.
4. 管理移徙
该插件添加 phoneNumber 财务报告和审定财务报表 phoneNumberVerified 页:1 user 页:1
短消息频道需要放置一个已核实的号码和更好的 Auth 用户
模型没有一栏。 名字和Auth的内置一致
phoneNumber() 插件是故意的,所以运行两个应用程序都会有一个
页:1.
npx @better-auth/cli migrate
用这个
"use client";
import { useState } from "react";
import { getAttemptsRemaining } from "@authlocker/better-auth/client";
import { authClient } from "@/lib/auth-client";
export function SignIn() {
const [verificationId, setVerificationId] = useState<string | null>(null);
const [attemptsLeft, setAttemptsLeft] = useState(5);
const [message, setMessage] = useState("");
async function send(to: string) {
const { data, error } = await authClient.authlocker.sendOtp({
channel: "email",
to,
});
if (error) return setMessage(error.message ?? "Could not send a code");
setVerificationId(data.verificationId);
// `data.to` is masked — "a**@example.com". Safe to show back.
setMessage(`Code sent to ${data.to}`);
}
async function verify(code: string) {
const { error } = await authClient.authlocker.verifyOtp({
verificationId: verificationId!,
code,
});
if (!error) return; // Session cookie is set. You are signed in.
switch (error.code) {
case "INVALID_CODE": {
const left = getAttemptsRemaining(error) ?? 0;
setAttemptsLeft(left);
return setMessage(`Wrong code — ${left} left`);
}
case "VERIFICATION_LOCKED":
return setMessage("Too many tries. Start again.");
case "VERIFICATION_EXPIRED":
return setMessage("That code expired. Send a new one.");
default:
setMessage(error.message ?? "Something went wrong");
}
}
}
短信,通过 channel: "sms" 和E.164的电话号码(+15551234567) (中文(简体) ).
错误是 API
该插件通过AuthLocker的出错代码而不是平分它们, 因为一个签到的形式 真正不同的事情 来谈论每一个. 把它们全部折叠到一个赤裸的401——这就是手卷的代理端点 通常是的——使正确的UI无法构建.
error.code | 状态 | 这是什么意思 |
|---|---|---|
INVALID_CODE | 400个 | 错误的代码。 还带着 attemptsRemaining。 。 。 . |
VERIFICATION_LOCKED | 409个 | 5次尝试。 终端——发送新代码. |
VERIFICATION_EXPIRED | 400个 | 过了10分钟TTL。 责无旁贷;提供再版. |
VERIFICATION_ALREADY_VERIFIED | 409个 | 单用. 通常为双提交表. |
RESEND_TOO_SOON | 429个 | 60秒窗内. |
RESEND_LIMIT_REACHED | 429个 | 3次重新进行核查. |
NOT_FOUND | 404 (韩语) | 未知或已收获 verificationId。 。 。 . |
SMS_REQUIRES_CLAIMED_CLIENT | 403 (韩语) | 见下文. |
SMS_COUNTRY_BLOCKED | 403 (韩语) | 此客户端没有启用拨号代码 . |
SMS_DAILY_CAP_REACHED | 429个 | 客户端自有"每日短信"封顶. |
USER_NOT_FOUND | 400个 | disableSignUp 没有人拥有那个地址。 密码是 对;不要告诉当事人这是错的. |
AUTHLOCKER_UNREACHABLE | 第503条 | 腾讯网. 故意在AuthLocker的词汇之外,所以一个DNS的失败永远不会被误认为是一个错误的代码. |
attemptsRemaining 以兄妹身份抵达 error 在电线上,不是里面.
插件将它提升到错误的正文上 getAttemptsRemaining(error) 从
@authlocker/better-auth/client 读回 — 更好的 Auth 客户端错误类型
固定在 { code, message, status, statusText },所以额外的字段到达
浏览器,但不包括类型,而这个帮助者是替代铸件的替代.
你发送的第一个短消息可能会失败
SMS_REQUIRES_CLAIMED_CLIENT 是最可能的首发失败.
AuthLocker只为归属于BotParty命名空间的客户发送短信,
因为每条消息都要花很多钱 电子邮件和 OAuth 的未声明工作
客户端,所以没有 警告你直到第一个 channel: "sms"。 。 。 .
向客户索赔 registration_access_token,或
npx botparty authlocker provision,设置 verify.dailySmsCap 当你
在那边.
插件还记录了像这样的配置类失败
ctx.context.logger 在服务器上, 不仅仅是呼叫者
第一次在浏览器内获取,其中半构件形式极有可能
吞下尸体,展示一个通用的红色土司.
社会登录 : genericOAuth,没有我们的插件
AuthLocker是符合标准的OIDC供应商,所以Better Auth的内置
genericOAuth 用发现处理Google和GitHub。 没有
这个包裹已经涉及到了.
import { betterAuth } from "better-auth";
import { genericOAuth } from "better-auth/plugins";
import { nextCookies } from "better-auth/next-js";
import { authlocker } from "@authlocker/better-auth";
export const auth = betterAuth({
database: /* your adapter */,
plugins: [
authlocker({
clientId: process.env.AUTHLOCKER_CLIENT_ID!,
clientSecret: process.env.AUTHLOCKER_CLIENT_SECRET!,
}),
genericOAuth({
// One entry per upstream provider, sharing a single client_id.
config: (["google", "github"] as const).map((provider) => ({
providerId: provider,
clientId: process.env.AUTHLOCKER_CLIENT_ID!,
clientSecret: process.env.AUTHLOCKER_CLIENT_SECRET!,
discoveryUrl:
"https://authlocker.dev/.well-known/openid-configuration",
// The discovered authorization_endpoint is /oauth/authorize, which
// answers 400 unless the request names a provider.
authorizationUrlParams: { provider },
scopes: ["openid", "email", "profile"],
// Defaults to true in 1.7. Written out because AuthLocker *requires*
// PKCE — leaving it implicit hides a hard requirement.
pkce: true,
})),
}),
nextCookies(),
],
});
await authClient.signIn.social({ provider: "google" });
6个容易出错的东西,都会产生混乱 失败,而不是明显的失败:
- 每个上游供应商一个配置,一个不叫
"authlocker"。 。 。 . 这个 发现authorization_endpoint实值/oauth/authorize,和 AuthLocker 键 拒绝一个不指定提供者的请求——所以每个条目都需要authorizationUrlParams: { provider: "google" }编辑providerId同时是 您所传递的字符串signIn.social()还有<SocialButtons>从 登记发送,所以命名他们"google"财务报告和审定财务报表"github"。 。 。 . - 回电是
{baseURL}/api/auth/callback/{providerId}——每笔 输入,所以/api/auth/callback/google财务报告和审定财务报表/api/auth/callback/github选择/api/auth/oauth2/callback/...,这是前1.7,也是大多数博客贴出的内容 仍然显示。 用 AuthLocker 注册确切的字符串 :redirect_uri实值 准确匹配,登记后不能更改,不登记 再来一次. - 奥米
redirectURI让更好的Auth从中得来baseURL编辑 上游的文献对此自相矛盾 匹配上面的路径. authClient.signIn.social({ provider })。 。 。 .signIn.oauth2()不再继续 已存在.- 没有
genericOAuthClient()再来一次. 客户端没有要添加的内容 用于社交登录. nextCookies()最后一个,如上. (中文(简体) ).
每个应用程序注册客户端一次,而不是每个功能一次
AuthLocker 问题 双向 主题标识符,取自
client_id :: provider :: upstream user id。注册客户两次和
同一个Google账户在两个不同的下方到达 subs, 哪个更好的 Auth
存储为两个账户,没有投诉。 每个应用程序一个注册——两个
genericOAuth 上面的条目是罚款 完全因为他们共享 a
client_id;只有上游供应商不同,谷歌的身份和
GitHub的身份总是两个身份.
已经使用更好的 Auth emailOTP()? 吗?
你无法做到 emailOTP() 我是奥特洛克的代表 它已经 没有 verifyOTP
钩子 它生成代码,储存在 verification 表格中,
盘点尝试,自己检查, 让你的回话来完成交付
只有这个 连接 AuthLocker 进入 sendVerificationOTP 会降级到邮件中
中继 : 消息中的代码将是更好的 Auth , 而 AuthLocker 的欺诈
计分,尝试关闭 和每个目标油门永远不会运行.
内置 phoneNumber() 插件 已经 有个 verifyOTP 钩子和可以
代表适当的短信。 如果你已经是了,这才是真正的选择
投资了它,但它覆盖一个频道, 所以你最后会有两个
不同的客户端 API 为相同的流量, 它仍然留下电子邮件未授权 .
此插件以一个形状覆盖两个频道 .
如果你坚持 phoneNumber() 与此插件并列, 共享 phoneNumber
栏是有意的,两个定义合并.
选项
authlocker({
clientId: string,
clientSecret: string,
// Base URL of the AuthLocker deployment. Default "https://authlocker.dev".
issuer?: string,
// Refuse to provision a user for an address nobody owns yet, so a correct
// code signs in an existing account or fails with USER_NOT_FOUND.
// Off by default: passwordless OTP sign-in is normally also sign-up.
disableSignUp?: boolean,
// User column holding a verified E.164 number. Default "phoneNumber".
phoneNumberField?: string,
// Placeholder address for a user who signed up by SMS — Better Auth's user
// model requires a unique email and an SMS sign-up has none. The default
// builds one under `.invalid`, which RFC 2606 reserves so it can never
// resolve. It does copy the number into a second column; override this if
// that matters to you.
getTempEmail?: (phoneNumber: string) => string,
// Display name for a newly created user. Empty by default — prompt for a
// real one afterwards rather than guessing from an address.
getName?: (to: string, channel: "email" | "sms") => string,
})
限制费率
插件声明 :
| 路径 | 窗口 | 马克斯 |
|---|---|---|
/authlocker/send-otp, (中文(简体) ), /authlocker/resend-otp | 60岁 | 页:1 |
/authlocker/verify-otp | 60岁 | 10个 |
二事须知. 更好的 Auth 应用利率限制 仅生产 以
默认( e)rateLimit.enabled),这些是惰性的 next dev和一个
应用程序级 rateLimit.customRules 其中一条路径的条目将覆盖
插件集,这是支持的收紧或松动它们的方法 :
betterAuth({
rateLimit: {
customRules: {
"/authlocker/send-otp": { window: 3600, max: 10 },
},
},
});
服务器端呼叫
每个终点都在 auth.api 此外,对于服务器动作和路由处理器:
const verification = await auth.api.sendOtp({
body: { channel: "email", to: "[email protected]" },
});
什麽 verify-otp 返回时
{ verification, token, user }。 。 。 . token 财务报告和审定财务报表 user 是其他更好的东西
认证登录返回; verification 是因为 @authlocker/react 类型
AuthLockerAdapter.checkVerification 以 Verification 财务报告和审定财务报表
把它开走 如果没有正确的代码, 将失败的解析
并显示为一个错误 对于已经获得一个会话饼干的人.
没有记录的东西
地址和代码,永远. verify/check 是一个 AuthLocker 响应
- 代码刚刚证明收件人
收到它——这个插件将这个值视为查找或创建输入:永远
已登录, 并且除属于用户行外, 无处写入 。 它确实会回来
打开 verification.to键入一个浏览器
与a并列 user 已经带有同一地址;这就是允许
onVerified 显示它自己的文件承诺。 到处都是 to 实值
已蒙上面罩( n)a**@example.com, (中文(简体) ), +*******6789) (中文(简体) ).
许可证
麻省理工学院