[{"data":1,"prerenderedAt":404},["ShallowReactive",2],{"content:\u002Fposts\u002F2025\u002Fbetter-auth":3,"surround:\u002Fposts\u002F2025\u002Fbetter-auth":392},{"id":4,"title":5,"body":6,"canonical":371,"categories":372,"date":374,"description":375,"draft":376,"extension":377,"image":378,"meta":379,"navigation":380,"path":381,"permalink":371,"readingTime":382,"recommend":371,"references":371,"seo":387,"sitemap":388,"stem":389,"tags":390,"type":371,"updated":374,"__hash__":391},"content\u002Fposts\u002F2025\u002Fbetter-auth.md","hono 集成 Better Auth",{"type":7,"value":8,"toc":337},"minimark",[9,33,42,47,50,56,65,69,74,83,94,103,111,118,126,130,138,142,151,159,166,170,176,183,187,195,199,205,209,216,224,227,236,240,256,259,262,265,271,274,279,282,292,299,302,310,312,315,318,321,325,330],[10,11,12,13,20,21,26,27,32],"p",{},"起始项目使用 ",[14,15,19],"a",{"href":16,"rel":17},"https:\u002F\u002Fsyntax.fm\u002F",[18],"nofollow","syntax"," 团队成员 ",[14,22,25],{"href":23,"rel":24},"https:\u002F\u002Fgithub.com\u002Fw3cj",[18],"CJ"," 的 ",[14,28,31],{"href":29,"rel":30},"https:\u002F\u002Fgithub.com\u002Fw3cj\u002Fhono-open-api-starter",[18],"hono-open-api-starter","。你可以去 GitHub 去下载源码和我一起做。",[10,34,35,36,41],{},"这里只演示使用 email 登录注册的功能，具体功能你可以去 ",[14,37,40],{"href":38,"rel":39},"https:\u002F\u002Fwww.better-auth.com\u002F",[18],"Better Auth 文档"," 查看",[43,44,46],"h2",{"id":45},"为什么选择-better-auth","为什么选择 Better Auth",[10,48,49],{},"我们看它官方的说法：",[51,52,53],"blockquote",{},[10,54,55],{},"Better Auth 是一个与框架无关的 TypeScript 身份验证和授权框架。它提供了一套全面的开箱即用功能，并包含一个插件生态系统，简化了高级功能的添加。无论您需要双因素身份验证 (2FA)、多租户、多会话支持，还是像单点登录 (SSO) 这样的企业级功能，它都能让您专注于构建应用程序，而无需重复造轮子。",[10,57,58,59,64],{},"而且它在 ",[14,60,63],{"href":61,"rel":62},"https:\u002F\u002Fgithub.com\u002Fbetter-auth\u002Fbetter-auth",[18],"GitHub"," 上很活跃，更新速度也蛮快。",[43,66,68],{"id":67},"better-auth-的集成","Better Auth 的集成",[70,71,73],"h3",{"id":72},"对-hono-open-api-starter-项目的改造","对 hono-open-api-starter 项目的改造",[10,75,76,77,82],{},"我这里使用 ",[14,78,81],{"href":79,"rel":80},"https:\u002F\u002Fwww.postgresql.org\u002F",[18],"postgresql"," 数据库，所以需要安装一些依赖",[84,85,91],"pre",{"className":86,"code":88,"language":89,"meta":90},[87],"language-shell","pnpm add drizzle-orm pg dotenv\npnpm add -D drizzle-kit tsx @types\u002Fpg\n","shell","",[92,93,88],"code",{"__ignoreMap":90},[95,96,98,99,102],"h4",{"id":97},"相应的-dbindexts-需要改造","相应的 ",[92,100,101],{"code":101},"db\u002Findex.ts"," 需要改造",[84,104,109],{"className":105,"code":107,"filename":101,"language":108,"meta":90},[106],"language-ts","import { drizzle } from \"drizzle-orm\u002Fnode-postgres\";\n\nimport env from \"@\u002Fhelper\u002Fenv\";\n\nimport * as schema from \".\u002Fschema\u002Findex\";\n\nimport \"dotenv\u002Fconfig\";\n\nconst db = drizzle({\n    connection: {\n        connectionString: env.DATABASE_URL,\n    },\n    schema,\n});\n\nexport { db };\n","ts",[92,110,107],{"__ignoreMap":90},[95,112,114,117],{"id":113},"drizzleconfigts-文件改动",[92,115,116],{"code":116},"drizzle.config.ts"," 文件改动",[84,119,124],{"className":120,"code":121,"filename":116,"highlights":122,"language":108,"meta":90},[106],"import { defineConfig } from \"drizzle-kit\";\n\nimport env from \".\u002Fsrc\u002Fhelper\u002Fenv\";\n\nimport \"dotenv\u002Fconfig\";\n\nexport default defineConfig({\n    out: \".\u002Fsrc\u002Fdb\u002Fmigrations\",\n    schema: \".\u002Fsrc\u002Fdb\u002Fschema\u002F**.ts\",\n    dialect: \"postgresql\",\n    dbCredentials: {\n        url: env.DATABASE_URL,\n    },\n});\n",[123],10,[92,125,121],{"__ignoreMap":90},[70,127,129],{"id":128},"安装-better-auth","安装 Better Auth",[84,131,136],{"className":132,"code":134,"language":135,"meta":90},[133],"language-bash","pnpm add better-auth\n","bash",[92,137,134],{"__ignoreMap":90},[70,139,141],{"id":140},"配置-env-文件","配置 .env 文件",[84,143,149],{"className":144,"code":146,"filename":147,"language":148,"meta":90},[145],"language-dotenv","BETTER_AUTH_SECRET= # 可以在文档中生成一个随机字符串使用\nBETTER_AUTH_URL=http:\u002F\u002Flocalhost:9999 # 后端地址\nDOMAIN=localhost # 项目启动域名\nFRONT_END_URL=http:\u002F\u002Flocalhost:5173 # 前端地址\n",".env","dotenv",[92,150,146],{"__ignoreMap":90},[70,152,154,155,158],{"id":153},"创建-authts-文件","创建 ",[92,156,157],{"code":157},"auth.ts"," 文件",[84,160,164],{"className":161,"code":162,"filename":163,"language":108,"meta":90},[106],"import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth\u002Fadapters\u002Fdrizzle\";\nimport { admin, openAPI, organization } from \"better-auth\u002Fplugins\";\n\nexport const auth = betterAuth({\n    database: drizzleAdapter(db, {\n        \u002F\u002F 这里我使用了 pg 数据库，所以这里填 pg\n        provider: \"pg\",\n    }),\n    \u002F\u002F openAPI 插件可以让我们在项目启动后使用 项目地址 + \u002Fapi\u002Fauth\u002Freference 访问其提供的 api\n    \u002F\u002F 这里我还启用了 admin 和 organization 插件\n    plugins: [openAPI(), admin(), organization()],\n})\n","src\u002Fauth.ts",[92,165,162],{"__ignoreMap":90},[70,167,169],{"id":168},"生成相关-schema","生成相关 schema",[84,171,174],{"className":172,"code":173,"language":89,"meta":90},[87],"npx @better-auth\u002Fcli generate\n",[92,175,173],{"__ignoreMap":90},[10,177,178,179,182],{},"生成的 schema 会在项目根目录中，我们将其移至 ",[92,180,181],{"code":181},"db\u002Fschema\u002F"," 文件夹中",[70,184,186],{"id":185},"使用-drizzle-相关命令来迁移数据库改动","使用 drizzle 相关命令来迁移数据库改动",[84,188,193],{"className":189,"code":191,"language":192},[190],"language-text","pnpm drizzle-kit generate\npnpm drizzle-kit migrate\n","text",[92,194,191],{"__ignoreMap":90},[70,196,198],{"id":197},"完善-authts-文件","完善 auth.ts 文件",[84,200,203],{"className":201,"code":202,"language":108,"meta":90},[106],"import { betterAuth } from \"better-auth\";\nimport { drizzleAdapter } from \"better-auth\u002Fadapters\u002Fdrizzle\";\nimport { admin, openAPI, organization } from \"better-auth\u002Fplugins\";\n\nimport type { AppOpenAPI } from \"@\u002Flib\u002Ftypes\";\n\nimport { db } from \"@\u002Fdb\u002Findex\";\nimport env from \"@\u002Fhelper\u002Fenv\";\n\nexport const auth = betterAuth({\n    database: drizzleAdapter(db, {\n        \u002F\u002F 这里我使用了 pg 数据库，所以这里填 pg\n        provider: \"pg\",\n    }),\n    \u002F\u002F 这里配置是使用 邮箱和密码 登录注册\n    emailAndPassword: {\n        enabled: true,\n    },\n    \u002F\u002F openAPI 插件可以让我们在项目启动后使用 项目地址 + \u002Fapi\u002Fauth\u002Freference 访问其提供的 api\n    \u002F\u002F 这里我还启用了 admin 和 organization 插件\n    plugins: [openAPI(), admin(), organization()],\n    \u002F\u002F 由于我们使用的是前后端分离的方式，所以我们需要配置 trustedOrigins\n    trustedOrigins: [env.BETTER_AUTH_URL, env.FRONT_END_URL],\n    \u002F\u002F 这里是需要配置 cookie 的同域名下共享\n    advanced: {\n        crossSubDomainCookies: {\n            enabled: true,\n            domain: env.DOMAIN,\n        },\n        defaultCookieAttributes: {\n            secure: true,\n            httpOnly: true,\n            sameSite: \"none\",\n            partitioned: true,\n        },\n    },\n})\n\n\u002F\u002F 挂载相关 api\nexport function authRoutes(app: AppOpenAPI) {\n    app.all(\"\u002Fapi\u002Fauth\u002F*\", c => auth.handler(c.req.raw));\n}\n",[92,204,202],{"__ignoreMap":90},[70,206,208],{"id":207},"登录中间件使用后可以在后续的请求中在-context-中访问-user-和-session-信息","登录中间件，使用后可以在后续的请求中在 context 中访问 user 和 session 信息",[84,210,214],{"className":211,"code":212,"filename":213,"language":108,"meta":90},[106],"import { cors } from \"hono\u002Fcors\";\n\nimport type { AppOpenAPI } from \"@\u002Flib\u002Ftypes\";\n\nimport { auth } from \"@\u002Fauth\";\nimport env from \"@\u002Fhelper\u002Fenv\";\n\nexport function authMiddleware(app: AppOpenAPI) {\n    app.use(\n        \"\u002Fapi\u002Fauth\u002F*\", \u002F\u002F or replace with \"*\" to enable cors for all routes\n        cors({\n            origin: env.FRONT_END_URL, \u002F\u002F replace with your origin\n            allowHeaders: [\"Content-Type\", \"Authorization\"],\n            allowMethods: [\"POST\", \"GET\", \"OPTIONS\"],\n            exposeHeaders: [\"Content-Length\"],\n            maxAge: 600,\n            credentials: true,\n        }),\n    );\n    app.use(\"*\", async (c, next) => {\n        const session = await auth.api.getSession({ headers: c.req.raw.headers });\n        if (!session) {\n            c.set(\"user\", null);\n            c.set(\"session\", null);\n            return next();\n        }\n\n        c.set(\"user\", session.user);\n        c.set(\"session\", session.session);\n        return next();\n    });\n}\n","middleware\u002Fsign-in.ts",[92,215,212],{"__ignoreMap":90},[95,217,219,220,223],{"id":218},"对-appopenapi-的类型做补充","对 ",[92,221,222],{"code":222},"AppOpenAPI"," 的类型做补充",[10,225,226],{},"解决 ts 类型报错的问题",[84,228,234],{"className":229,"code":230,"filename":231,"highlights":232,"language":108,"meta":90},[106],"import type { OpenAPIHono, RouteConfig, RouteHandler } from \"@hono\u002Fzod-openapi\";\nimport type { PinoLogger } from \"hono-pino\";\n\nimport type { auth } from \"@\u002Fauth\";\n\nexport interface AppBindings {\n    Variables: {\n        logger: PinoLogger\n        user: typeof auth.$Infer.Session.user | null\n        session: typeof auth.$Infer.Session.session | null\n    }\n}\n\nexport type AppOpenAPI = OpenAPIHono\u003CAppBindings>;\n\nexport type AppRouteHandler\u003CR extends RouteConfig> = RouteHandler\u003CR, AppBindings>;\n","lib\u002Ftypes.ts",[233,123],9,[92,235,230],{"__ignoreMap":90},[70,237,239],{"id":238},"在主路由中使用-better-auth","在主路由中使用 Better Auth",[84,241,254],{"className":242,"code":243,"filename":244,"highlights":245,"language":108,"meta":90},[106],"import { cors } from \"hono\u002Fcors\";\n\nimport { authRoutes } from \"@\u002Fauth\";\nimport env from \"@\u002Fhelper\u002Fenv\";\nimport configureOpenAPI from \"@\u002Flib\u002Fconfigure-open-api\";\nimport createApp from \"@\u002Flib\u002Fcreate-app\";\nimport { authMiddleware } from \"@\u002Fmiddleware\u002Fsign-in\";\nimport index from \"@\u002Froutes\u002Findex.route\";\nimport tasks from \"@\u002Froutes\u002Ftasks\u002Ftasks.index\";\n\nconst app = createApp();\n\nconfigureOpenAPI(app);\n\nauthMiddleware(app);\nauthRoutes(app);\n\napp.use(\n    \"*\",\n    cors({ origin: env.FRONT_END_URL, credentials: true }),\n);\n\nconst routes = [\n    index,\n    tasks,\n] as const;\nroutes.forEach(route => app.route(\"\u002Fapi\", route));\n\nexport type AppType = typeof routes[number];\n\nexport default app;\n","app.ts",[246,247,233,248,249,250,251,252,253],1,6,15,16,18,19,20,21,[92,255,243],{"__ignoreMap":90},[257,258],"hr",{},[43,260,261],{"id":261},"前端使用",[10,263,264],{},"前端这里我使用 vite 搭建一个简单的项目来进行演示",[84,266,269],{"className":267,"code":268,"language":89,"meta":90},[87],"pnpm create vite --template vue-ts\n",[92,270,268],{"__ignoreMap":90},[70,272,129],{"id":273},"安装-better-auth-1",[84,275,277],{"className":276,"code":134,"language":89,"meta":90},[87],[92,278,134],{"__ignoreMap":90},[70,280,281],{"id":281},"使用",[10,283,284,285,288,289],{},"使用 Better Auth 提供的工具来访问它提供的接口是很方便的。我们需要在 ",[92,286,287],{"code":287},"\u002Fsrc\u002F"," 下创建一个 ",[92,290,291],{"code":291},"auth-client",[84,293,297],{"className":294,"code":295,"filename":296,"language":108,"meta":90},[106],"import { adminClient, organizationClient } from \"better-auth\u002Fclient\u002Fplugins\"\nimport { createAuthClient } from \"better-auth\u002Fvue\"\n\nexport const authClient = createAuthClient({\n    baseURL: \"http:\u002F\u002Flocalhost:9999\",\n    plugins: [\n    \u002F\u002F 这里如果后端有一些better-auth插件，这里也要引入对应的前端插件\n        organizationClient(),\n        adminClient()\n    ]\n})\n","src\u002Flib\u002Fauth-client.ts",[92,298,295],{"__ignoreMap":90},[10,300,301],{},"创建好后，我们就可以在页面中使用它了",[84,303,308],{"className":304,"code":306,"language":307,"meta":90},[305],"language-vue","\u003Cscript setup lang=\"ts\">\nimport { ref } from \"vue\"\n\nimport { authClient } from \".\u002Flib\u002Fauth-client\"\n\nconst loginInfo = ref()\nconst loginError = ref()\n\n\u002F\u002F 登录\nasync function signIn() {\n    const { data, error } = await authClient.signIn.email({\n        email: \"test@example.com\",\n        password: \"admin888\",\n    })\n    loginInfo.value = data\n    loginError.value = error\n}\n\u002F\u002F 注册\nasync function signUp() {\n    const { data, error } = await authClient.signUp.email({\n        email: \"test@example.com\",\n        password: \"admin888\",\n        name: \"test user\",\n    })\n    loginInfo.value = data\n    loginError.value = error\n}\nconst session = await authClient.getSession()\n\n\u002F\u002F 登录用户创建组织\nasync function createOrganization() {\n    if (!authClient.getSession()) {\n        alert(\"Please sign in first\")\n        return\n    }\n\n    await authClient.organization.create({\n        name: \"test organization\",\n        slug: \"test-organization\",\n    })\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n    \u003Cbutton @click=\"signIn\">\n        登录\n    \u003C\u002Fbutton>\n    \u003Cbutton @click=\"signUp\">\n        注册\n    \u003C\u002Fbutton>\n    \u003Cbutton @click=\"createOrganization\">\n        创建组织\n    \u003C\u002Fbutton>\n    loginInfo:{{ loginInfo }} loginError:{{ loginError }}\n    \u003Chr>\n    {{ session }}\n\u003C\u002Ftemplate>\n","vue",[92,309,306],{"__ignoreMap":90},[257,311],{},[43,313,314],{"id":314},"可能遇到的问题",[70,316,317],{"id":317},"跨域",[10,319,320],{},"因为是前后端分离项目，所以在使用时需要配置跨域相关内容，以上文章都有说明。跨域问题和 cookie 相关配置需要谨慎，最好限制指定域名跨域。",[70,322,324],{"id":323},"前后端不同源时前端使用-better-auth-需要配置不推荐","❌前后端不同源时，前端使用 better-auth 需要配置（不推荐）",[51,326,327],{},[10,328,329],{},"这是我开始使用的一种方式，后端如果没有配置好跨域和 cookie 相关内容，会导致登录后使用 Better Auth clieht 相关的获取 session 的方法无法拿到对应的 session",[84,331,335],{"className":332,"code":333,"highlights":334,"language":108,"meta":90},[106],"import { createAuthClient } from \"better-auth\u002Fvue\"\n\nexport const authClient = createAuthClient({\n    \u002F** The base URL of the server (optional if you're using the same domain) *\u002F\n    baseURL: \"http:\u002F\u002Flocalhost:9999\",\n    fetchOptions: {\n        credentials: 'omit'\n    }\n})\n",[247],[92,336,333],{"__ignoreMap":90},{"title":90,"searchDepth":338,"depth":338,"links":339},4,[340,342,363,367],{"id":45,"depth":341,"text":46},2,{"id":67,"depth":341,"text":68,"children":343},[344,351,352,353,355,356,357,358,362],{"id":72,"depth":345,"text":73,"children":346},3,[347,349],{"id":97,"depth":338,"text":348},"相应的 db\u002Findex.ts 需要改造",{"id":113,"depth":338,"text":350},"drizzle.config.ts 文件改动",{"id":128,"depth":345,"text":129},{"id":140,"depth":345,"text":141},{"id":153,"depth":345,"text":354},"创建 auth.ts 文件",{"id":168,"depth":345,"text":169},{"id":185,"depth":345,"text":186},{"id":197,"depth":345,"text":198},{"id":207,"depth":345,"text":208,"children":359},[360],{"id":218,"depth":338,"text":361},"对 AppOpenAPI 的类型做补充",{"id":238,"depth":345,"text":239},{"id":261,"depth":341,"text":261,"children":364},[365,366],{"id":273,"depth":345,"text":129},{"id":281,"depth":345,"text":281},{"id":314,"depth":341,"text":314,"children":368},[369,370],{"id":317,"depth":345,"text":317},{"id":323,"depth":345,"text":324},null,[373],"代码","2025-04-24 17:47:42","Better Auth 是一个与框架无关的 TypeScript 身份验证和授权框架。它提供了一套全面的开箱即用功能，并包含一个插件生态系统，简化了高级功能的添加。本文我们将使用 hono 集成它，并在 vue 中使用它。",false,"md","https:\u002F\u002Fbitmc.uno\u002Fpicgo\u002Fbetter-auth-with-hono.png",{},true,"\u002Fposts\u002F2025\u002Fbetter-auth",{"text":383,"minutes":384,"time":385,"words":386},"8 min read",7.415,444900,1483,{"title":5,"description":375},{"loc":381},"posts\u002F2025\u002Fbetter-auth",[],"q7ZSzLlh_nFomlBi2pTz-7LzR-L7M_PmFz5bVZuAwUw",[393,399],{"title":394,"path":395,"stem":396,"date":397,"type":398,"children":-1},"shadcn-vue-admin 说明","\u002Fposts\u002F2025\u002Fabout-shadcn-vue-admin","posts\u002F2025\u002Fabout-shadcn-vue-admin","2025-04-20 16:38:09","story",{"title":400,"path":401,"stem":402,"date":403,"type":371,"children":-1},"使用 chatgpt 制作自己的小贴纸","\u002Fposts\u002F2025\u002Fcreate-own-sticker-use-chatgpt","posts\u002F2025\u002Fcreate-own-sticker-use-chatgpt","2025-04-27 10:56:14",1789642589657]