资讯动态

Better Auth 设备授权插件怎么配置,让 CLI 和 IoT 设备获得会话令牌

发布时间:2026/9/12 18:01:30 来源:尧图企业网站定制
Better Auth 设备授权插件怎么配置让 CLI 和 IoT 设备获得会话令牌【免费下载链接】better-authThe most comprehensive authentication framework项目地址: https://gitcode.com/GitHub_Trending/be/better-auth如果你的产品里有无法弹出浏览器登录框的客户端——CLI 工具、智能电视、IoT 设备、游戏主机——就需要一套设备先拿码、用户在别的浏览器里批准的登录方式。Better Auth 的 Device Authorization 插件实现了 RFC 8628OAuth 2.0 Device Authorization Grant完成配置后CLI 或 IoT 设备最终能从/device/token拿到 Better Auth 会话令牌session token用这个令牌就能以该用户身份调用你自己的 API。适用前提你已有一个运行 Better Auth 的 Node.js 服务端并且配置了数据库大多数插件功能都依赖数据库。本文走的是第一方会话路径即/device/token返回 Better Auth 会话令牌如果你的 CLI 是注册过的 OAuth 公共客户端、需要带 scope 的 JWT 访问令牌文档给出了基于oauthProvider()的另一条路径见文末说明。1. 准备工作安装 Better Auth 并设置环境变量先按 安装文档 完成基础配置npm install better-auth如果是前后端分离的项目客户端和服务端两边都要安装。然后在项目根目录的.env中配置两个必需变量BETTER_AUTH_SECRET至少 32 字符的高熵密钥可用 openssl rand -base64 32 生成 BETTER_AUTH_URLhttp://localhost:3000 # 应用的 Base URL接着在auth.ts可放在项目根目录、lib/、utils/或其下的src/、app/、server/目录中创建实例并配置一个数据库例如 SQLiteimport { betterAuth } from better-auth; import Database from better-sqlite3; export const auth betterAuth({ database: new Database(./sqlite.db), });PostgreSQL、MySQL 等其他数据库的写法见 安装文档。2. 在 auth.ts 中注册设备授权插件import { betterAuth } from better-auth; import { deviceAuthorization } from better-auth/plugins; export const auth betterAuth({ // ...其他配置 plugins: [ deviceAuthorization({ verificationUri: /device, }), ], });verificationUri是用户输入设备代码的验证页面地址必须是绝对 URL如https://example.com/device或相对路径如/device默认值就是/device。响应里的verification_uri和verification_uri_complete带user_code查询参数都由它拼出来所以这里要填成你真实验证页的路由。其他可用的服务端选项及默认值选项默认值说明expiresIn30m设备代码的有效期interval5s设备轮询的最小间隔userCodeLength8用户代码长度最大 191deviceCodeLength40设备代码长度最大 191validateClient无校验client_id的函数返回 boolean生产环境文档要求务必配置防止未授权应用发起设备流程generateDeviceCode/generateUserCode内置生成器自定义代码生成函数onDeviceAuthRequest无每次有设备发起授权请求时触发的钩子可用来记录日志3. 迁移数据库建出 deviceCode 表插件需要一张新表来存储设备授权数据运行迁移命令生成npx auth migrate或者用npx auth generate生成 schema 后自行建表。表名是deviceCode字段包括id主键、deviceCode和userCode都是唯一索引最长 191 字符、userId、clientId、scope、statuspending/approved/denied、expiresAt、lastPolledAt、pollingInterval。完整字段定义见插件文档的 Schema 章节。4. 客户端注册 deviceAuthorizationClient在客户端auth-client.ts加入对应的客户端插件它会暴露出流程所需的全部方法import { createAuthClient } from better-auth/client; import { deviceAuthorizationClient } from better-auth/client/plugins; export const authClient createAuthClient({ plugins: [ deviceAuthorizationClient(), ], });客户端插件没有独立配置项注册后可用device.code()申请 device code 和 user codePOST/device/codedevice.token()轮询换令牌POST/device/tokendevice()校验用户代码是否有效GET/devicedevice.approve()/device.deny()批准或拒绝设备均需已登录会话5. 设备端请求设备代码并按 interval 轮询整个流程是设备请求代码 → 用户在验证页批准 → 设备轮询拿到令牌。下面是文档中给出的完整 CLI 示例client_id用的是文档示例值demo-cli请替换成你自己为设备端约定的标识baseURL换成你的服务端地址import { createAuthClient } from better-auth/client; import { deviceAuthorizationClient } from better-auth/client/plugins; import open from open; const authClient createAuthClient({ baseURL: http://localhost:3000, plugins: [deviceAuthorizationClient()], }); async function authenticateCLI() { // 1. 请求设备代码 const { data, error } await authClient.device.code({ client_id: demo-cli, scope: openid profile email, }); if (error || !data) { console.error(❌ Error:, error?.error_description); process.exit(1); } const { device_code, user_code, verification_uri, verification_uri_complete, interval 5, } data; console.log(Please visit: ${verification_uri}); console.log(Enter code: ${user_code}\n); // 打开浏览器到验证页可选也可只打印地址 await open(verification_uri_complete || verification_uri); console.log(Waiting for authorization... (polling every ${interval}s)); // 2. 轮询令牌 await pollForToken(device_code, interval); } async function pollForToken(deviceCode: string, interval: number) { let pollingInterval interval; return new Promisevoid((resolve) { const poll async () { try { const { data, error } await authClient.device.token({ grant_type: urn:ietf:params:oauth:grant-type:device_code, device_code: deviceCode, client_id: demo-cli, }); if (data?.access_token) { console.log(Authorization Successful! Access token received!); // 3. 用拿到的会话令牌验证会话是否可用 const { data: session } await authClient.getSession({ fetchOptions: { headers: { Authorization: Bearer ${data.access_token}, }, }, }); console.log(Hello, ${session?.user?.name || User}!); resolve(); } else if (error) { switch (error.error) { case authorization_pending: break; // 用户还没批准继续轮询 case slow_down: pollingInterval 5; // 轮询太频繁加大间隔 break; case access_denied: console.error(Access was denied by the user); process.exit(1); break; case expired_token: console.error(The device code has expired. Please try again.); process.exit(1); break; default: console.error(Error:, error.error_description); process.exit(1); } } } catch (err) { console.error(Network error:, err.message); process.exit(1); } setTimeout(poll, pollingInterval * 1000); }; setTimeout(poll, pollingInterval * 1000); }); } authenticateCLI().catch((err) { console.error(Fatal error:, err); process.exit(1); });轮询时的错误码即文档定义的判断依据错误码含义设备端动作authorization_pending用户还没批准继续轮询slow_down轮询太频繁加大轮询间隔示例做法是 5 秒access_denied用户拒绝了授权终止流程expired_token设备代码已过期终止并提示用户重新发起invalid_grantdevice code 或 client ID 无效终止流程/device/token成功后返回的access_token是 Better Auth 会话令牌不是 RFC 8628 意义上的 OAuth 访问令牌直接以Authorization: Bearer access_token方式使用。若要拿它调用 API文档要求确保你的 auth 实例上已添加 Bearer 插件better-auth/plugins中的bearer()。device.code请求体还支持两个可选字段scope空格分隔的 scope 列表和user_id。user_id用于预绑定如果你的服务端已知设备归属哪个用户传入后该代码从发起时就绑定到该用户跳过认领步骤其他登录用户尝试批准会收到access_denied。文档提醒user_id只能从受信任的服务端代码传入因为它只限制谁能批准不可信设备不能借它访问别的账户。6. 用户端验证页和批准页用户在浏览器里需要两个页面对应verificationUri路由下的验证与批准。关键机制调用GET /device时必须已登录——验证请求会把待批准的设备代码认领到当前会话之后只有同一会话能批准或拒绝。所以如果用户没登录要重定向到登录页并带上返回地址登录回来后再调一次GET /device。文档给出的验证页示例export default function DeviceAuthorizationPage() { const { data: session } authClient.useSession(); const searchParams useSearchParams(); const [userCode, setUserCode] useState(searchParams.get(user_code) || ); const [error, setError] useState(null); const handleSubmit async (e) { e.preventDefault(); try { // 归一化去掉横线并转大写 const formattedCode userCode.trim().replace(/-/g, ).toUpperCase(); const approvalPath /device/approve?user_code${encodeURIComponent(formattedCode)}; if (!session?.user) { // 未登录先去登录登录回来再验证 const verificationPath /device?user_code${encodeURIComponent(formattedCode)}; window.location.href /login?redirect${encodeURIComponent(verificationPath)}; return; } // 用 GET /device 检查代码有效同时完成认领 const response await authClient.device({ query: { user_code: formattedCode }, }); if (response.data) { window.location.href approvalPath; } } catch (err) { setError(Invalid or expired code); } }; return ( form onSubmit{handleSubmit} input typetext value{userCode} onChange{(e) setUserCode(e.target.value)} placeholderEnter device code (e.g., ABCD-1234) maxLength{12} / button typesubmitContinue/button {error p{error}/p} /form ); }批准页文档示例位于app/device/approve/page.tsx先用authClient.device({ query: { user_code } })拉取请求信息展示给用户client_id、scope然后提供两个按钮分别调用await authClient.device.approve({ userCode: userCode }); // 或 await authClient.device.deny({ userCode: userCode });两个接口都要求已登录会话只有认领了该代码的那个会话能操作其他用户调用会得到access_denied。批准接口成功后返回{ success: true }此时设备端下一次轮询就能拿到access_token。用户代码默认不区分大小写输入时夹杂的空白和标点会被去除后再匹配所以用户可以放心输入ABCD-1234这种带横线的形式。7. 验证整条链路是否跑通按顺序核对四点设备端调用device.code后终端打印出verification_uri、user_code且interval有值默认 5 秒浏览器打开verification_uri_complete登录状态下输入代码能进入批准页页面上显示了发起方client_id和请求的scope点击 Approve 后返回success: true设备端下一次轮询拿到access_token用它调getSession能返回对应用户——上面的 CLI 示例会打印Hello, 用户名!这就是文档示例中跑通整个流程的判定方式。想快速体验一次完整流程文档提供了一个演示命令npx auth login这是 Better Auth CLI 的演示功能它会连接 Better Auth 的 demo 服务器走一遍完整的设备授权流程申请代码、展示 user code、打开浏览器、轮询完成可用于确认你对流程的理解不依赖自己的服务端。8. 限制、安全要求与可选分支文档明确给出的约束上线前逐条对照HTTPSRFC 8628 要求设备端出站请求和用户验证页都走 TLS 保护生产环境必须用 HTTPSHTTP 只用于本地开发。批准 UI 是安全边界批准页必须让用户输入user_code或核对verification_uri_complete展示的码、展示正在被授权的客户端和 scope、要求显式批准或拒绝并提醒用户不要批准陌生请求。限频/device在一个等于设备代码有效期的时间窗口内限 5 次请求/device/token有自己的轮询间隔和slow_down机制。过期与重试代码默认 30 分钟过期设备代码生成遇到唯一键冲突最多重试 3 次全部冲突时/device/code返回server_error。生产环境务必配置validateClient校验client_id否则任何字符串都能发起设备流程。迁移注意deviceCode和userCode列有唯一索引如果数据库里已有重复值MySQL 和 SQL Server 还需要把两列转成有界字符串并清理超过 191 字符的值。如果两条令牌路径需要并存第一方设备登录继续用/device/token拿会话令牌注册过的 OAuth 客户端用oauthProvider()oauthDeviceAuthorization()走/oauth2/token拿 scope 化的 JWT 访问令牌CLI 因此无法保存 client secret这类公共客户端用token_endpoint_auth_method: none并注册type: native。注意文档特别提醒OAuth 流程中不要调用authClient.device.token那个方法轮询的是/device/token且返回的是会话令牌。两种路径的详细配置见 Device Authorization 插件文档中的 Authorize a CLI to call an API 一节。【免费下载链接】better-authThe most comprehensive authentication framework项目地址: https://gitcode.com/GitHub_Trending/be/better-auth创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价