本指南概述了针对 Kinde 的特定设置,流程类似Convex & Clerk集成,但本指南的重点是讲解如何将 Kinde 集成到 Convex 中。
它解答了许多开发者的问题,这些问题是由Kinde开发者社区关心的。详情可在这里查看:Kinde 社区 - 将 Convex 与 Kinde 集成
该教程清晰地列出了如何使用Kinde认证与Convex集成的实际步骤,并按照最佳实践。
Kinde 是一个认证平台,支持无密码登录方式,如魔法链接(Magic Link)、短信验证码(SMS)或身份验证器应用程序(Authenticator App)。它还支持多因素认证(MFA)以增强安全性,支持基于 SAML 的企业级单点登录(SSO),并提供强大的用户管理工具,帮助企业更好地管理用户。
示例: 使用Kinde的凸形认证(点击这里:Convex Authentication with Kinde)
如果你在使用 Next.js,可以参考 Convex 的 Next.js 设置指南。
开始吧此指南假设你已经集成了 Convex 的可正常运行的 Next.js 应用。如果没有,请先参考 Convex Next.js 快速入门。然后:
- 注册 Kinde 账号
在kinde.com/register 注册一个免费的 Kinde 账户。
- 在Kindle创办一个企业
输入您公司或应用的名称:
- 选择你的技术组合
选择你用来构建此应用程序的技术栈和工具。
- 选择认证方式
选择你想要用户如何签入。
- 将您的应用连接到Kinde
让你的 Next.js 应用连接到 Kinde。
- 创建认证设置
从你的 .env.local 文件中复制 KINDE_ISSUER_URL 这个字段。进入 convex 文件夹后,然后创建一个新的 auth.config.ts 文件,用于设置服务器端验证访问令牌的配置信息。
粘贴 _KINDE_ISSUERURL 并将 applicationID 设置为 "convex" (无需修改 "aud" Claims 字段的值)。
    const authConfig = {
      providers: [
        {
          domain: process.env.KINDE_ISSUER_URL, // 例如:https://barswype.kinde.com
          applicationID: "应用ID",
        },
      ]
    };
    export default authConfig;
点击这里进入全屏模式 点击这里退出全屏模式
- 设置 Convex & Kinde Webhook
在 Kinde 仪表盘中,进入 设置 > Webhooks > 点击 添加 Webhook > 命名为 Webhook 并粘贴您的 Convex 端点 URL,例如 https://<您的-convex-app>.convex.site/kinde。
选择要触发的事件,比如“user.created”和“user.deleted”。
现在回到你的代码部分。打开你的 convex/ 文件夹,然后新建一个名为 http.ts 的文件,接着复制并粘贴这段代码。
    import { httpRouter } from "convex/server";
    import { internal } from "./_generated/api";
    import { httpAction } from "./_generated/server";
    import { jwtVerify, createRemoteJWKSet } from "jose";
    type KindeEventData = {
      user: {
        id: string;
        email: string;
        first_name?: string;
        last_name?: string | null;
        is_password_reset_requested: boolean;
        is_suspended: boolean;
        organizations: {
          code: string;
          permissions: string | null;
          roles: string | null;
        }[];
        phone?: string | null;
        username?: string | null;
        image_url?: string | null;
      };
    };
    type KindeEvent = {
      type: string;
      data: KindeEventData;
    };
    const http = httpRouter();
    const handleKindeWebhook = httpAction(async (ctx, request) => {
      const event = await validateKindeRequest(request);
      if (!event) {
        return new Response("无效请求", { status: 400 });
      }
      switch (event.type) {
        case "user.created":
          await ctx.runMutation(internal.users.createUserKinde, {
            kindeId: event.data.user.id,
            email: event.data.user.email,
            username: event.data.user.first_name || ""
          });
          break;
        {/** 
        case "user.updated":
          const existingUserOnUpdate = await ctx.runQuery(
            internal.users.getUserKinde,
            { kindeId: event.data.user.id }
          );
          if (existingUserOnUpdate) {
            await ctx.runMutation(internal.users.updateUserKinde, {
              kindeId: event.data.user.id,
              email: event.data.user.email,
              username: event.data.user.first_name || ""
            });
          } else {
            console.warn(
              `未找到需要更新的用户,其 kindeId 为 ${event.data.user.id}.`
            );
          }
          break;
        */}
        case "user.deleted":
          const userToDelete = await ctx.runQuery(internal.users.getUserKinde, {
            kindeId: event.data.user.id,
          });
          if (userToDelete) {
            await ctx.runMutation(internal.users.deleteUserKinde, {
              kindeId: event.data.user.id,
            });
          } else {
            console.warn(
              `未找到需要删除的用户,其 kindeId 为 ${event.data.user.id}.`
            );
          }
          break;
        default:
          console.warn(`未处理的事件类型:${event.type}`);
      }
      return new Response(null, { status: 200 });
    });
    // ===== JWT 验证部分 =====
    async function validateKindeRequest(request: Request): Promise<KindeEvent | null> {
      try {
        if (request.headers.get("content-type") !== "application/jwt") {
          console.error("无效的 Content-Type。期望 application/jwt");
          return null;
        }
        const token = await request.text(); // JWT 作为纯文本发送在请求体中。
        const JWKS_URL = `${process.env.KINDE_ISSUER_URL}/.well-known/jwks.json`;
        const JWKS = createRemoteJWKSet(new URL(JWKS_URL));
        const { payload } = await jwtVerify(token, JWKS);
        // 确保负载包含预期属性
        if (
          typeof payload === "object" &&
          payload !== null &&
          "type" in payload &&
          "data" in payload
        ) {
          return {
            type: payload.type as string,
            data: payload.data as KindeEventData,
          };
        } else {
          console.error("负载结构不符合预期要求");
          return null;
        }
      } catch (error) {
        console.error("JWT 认证失败", error);
        return null;
      }
    }
    http.route({
      path: "/kinde",
      method: "POST",
      handler: handleKindeWebhook,
    });
    export default http;
点击全屏按钮进入全屏 点击退出按钮退出全屏
请参阅这篇帖子以获取有关在 Kinde 和 Convex 之间设置 webhook 的详细指南:参阅这篇帖子。
- 应用你的更改
运行命令 npx convex dev 以让您的配置自动同步到后端。
在终端中运行此命令: npx convex dev进入全屏 退出全屏
- 安装 Kindle 电子书阅读器
在新的终端窗口里,打开并安装 Kinde Next.js 库(或npm包)。
    npm install @kinde-oss/kinde-auth-nextjs要安装这个包,请在终端中输入以上命令。@kinde-oss/kinde-auth-nextjs 是一个用于 Next.js 的 Kinde 认证插件。
点击全屏模式 点一下退出全屏
- 复制一下你的 Kindle 设置.
在 Kinde 仪表盘上,点击你应用的 查看详细信息。
往下滚动,然后复制你的 Client ID 和 Client secret
- 设置 Kinde 身份验证路由处理器
创建一个文件 app/api/auth/[kindeAuth]/route.ts 在你的 Next.js 项目里。在文件 route.ts 中复制以下代码:
    import {handleAuth} from "@kinde-oss/kinde-auth-nextjs/server";
    // 这里引入了身份验证处理函数,并设置了GET请求的处理方式
    export const GET = handleAuth();
全屏: 进入 全屏退出
这将处理你 Next.js 应用中的 Kinde 认证接口。
重要提示! Kinde SDK 依赖于该文件位于上述指定位置如下。
- 为 Convex 和 Kinde 集成配置一个新的提供商
在你的项目根目录下创建一个 providers 文件夹,并添加一个新的文件 ConvexKindeProvider.tsx。这个 provider 会集成 Convex 和 Kinde,并包裹整个应用。
在 ConvexKindeProvider.tsx 文件中,将 ConvexProvider 包裹在 KindeProvider 中,并使用 useKindeAuth 获取认证令牌,然后把它传给 Convex。
将 domain、clientId 和 redirectUri 传递给 KindeProvider。
"use client";
import { ReactNode, useEffect } from "react";
import { KindeProvider, useKindeAuth } from "@kinde-oss/kinde-auth-nextjs";
import { ConvexProvider, ConvexReactClient, AuthTokenFetcher } from "convex/react";
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL as string);
const ConvexKindeProvider = ({ children }: { children: ReactNode }) => {
  const { getToken } = useKindeAuth();
  useEffect(() => {
    const fetchToken: AuthTokenFetcher = async () => {
      const token = await getToken();
      return token || null;
    };
    if (typeof getToken === "function") {
      convex.setAuth(fetchToken);
    }
  }, [getToken]);
  return (
    <KindeProvider
      domain={process.env.NEXT_PUBLIC_KINDE_DOMAIN as string}
      clientId={process.env.NEXT_PUBLIC_KINDE_CLIENT_ID as string}
      redirectUri={process.env.NEXT_PUBLIC_KINDE_REDIRECT_URI as string}
    >
      <ConvexProvider client={convex}>{children}</ConvexProvider>
    </KindeProvider>
  );
};
export default ConvexKindeProvider;全屏模式 (按 esc 退出)
将配置好的 ConvexKindeProvider.tsx 导入到主 layout.tsx 文件中。
    import type { Metadata } from "next";
    import "./globals.css";
    import ConvexKindeProvider from "@/providers/ConvexKindeProvider";
    export const metadata: Metadata = {
      title: "创建 Next 应用",
      description: "Kinde 和 Convex 演示",
    };
    export default function RootLayout({
      children,
    }: Readonly<{
      children: React.ReactNode;
    }>) {
      return (
        <ConvexKindeProvider>
          <html lang="en">
            <body>
              {children}
            </body>
          </html>
        </ConvexKindeProvider>
      );
    };
全屏;退出全屏
- 基于认证状态显示界面
你可以使用来自"convex/react"和"@kinde-oss/kinde-auth-nextjs"的组件来控制显示哪个界面,这取决于用户是否已登录。
要开始,请创建一个允许用户登录和登出的shell。
因为 DisplayContent 组件是 Authenticated 的子组件,因此在其及其子组件内部,认证都是有保证的,Convex 查询也可以如此依赖。
    "use client";
    import { useKindeBrowserClient } from "@kinde-oss/kinde-auth-nextjs";
    import {
      RegisterLink,
      LoginLink,
      LogoutLink,
    } from "@kinde-oss/kinde-auth-nextjs/components";
    import { Authenticated, Unauthenticated, useQuery } from "convex/react";
    import { api } from "@/convex/_generated/api";
    function App() {
      const { isAuthenticated, getUser } = useKindeBrowserClient();
      const user = getUser();
      return (
        <main>
          <Unauthenticated>
            <LoginLink postLoginRedirectURL="/dashboard">点击登录</LoginLink>
            <RegisterLink postLoginRedirectURL="/welcome">点击注册</RegisterLink>
          </Unauthenticated>
          <Authenticated>
            {isAuthenticated && user ? (
              <div>
                <p>名: {user.given_name} {user.family_name}</p>
                <p>邮箱: {user.email}</p>
                <p>手机号码: {user.phone_number}</p>
              </div>
            ) : null}
            <DisplayContent />
            <LogoutLink>点击登出</LogoutLink>
          </Authenticated>
        </main>
      );
    }
    function DisplayContent() {
      const { user } = useKindeBrowserClient();
      const files = useQuery(api.files.getForCurrentUser, {
        kindeId: user?.id,
      });
      return <div>认证内容: {files?.length}</div>;
    }
    export default App;
全屏 退出全屏
- 在你的 Convex 函数中使用身份验证状态
如果用户通过身份验证,你可以通过 ctx.auth.getUserIdentity 获取 Kinde 发送的 JWT 中的用户信息。
如果用户未通过身份验证,ctx.auth.getUserIdentity 将返回 null。
确保调用此查询的组件是Authenticated的子组件,该Authenticated来自"convex/react",否则会在页面加载时抛出异常。
    import { query } from "./_generated/server";
    export const getForCurrentUser = query({
      args: { kindeId: v.string() },
      handler: async (ctx, args) => {
        const identity = await ctx.auth.getUserIdentity();
        if (identity === null) {
          throw new Error("未通过身份验证");
        }
        const files = await ctx.db
          .query("files")
          .filter((q) => q.eq(q.field("kindeId"), args.kindeId))
          .collect();
        if (!files) {
          throw new Error("未找到此用户的任何文件");
        }
        return files;
      },
    });
全屏 退出全屏
登录和退出流程现在你设置好了,你可以使用LoginLink组件来实现你的应用的登录功能。
如果您更喜欢为您的应用自定义登录或注册表单,参阅这篇帖子。
    import {LoginLink} from "@kinde-oss/kinde-auth-nextjs/components";
    <LoginLink>登录</LoginLink>
全屏模式(进入/退出)
你可以使用 LogoutLink 组件,让用户轻松地注销。
    import {LogoutLink} from "@kinde-oss/kinde-auth-nextjs/components";
    <LogoutLink>登出链接</LogoutLink>
切换到全屏 / 退出全屏
登录和未登录时的视图使用useConvexAuth()钩子而不是Kinde的useKindeBrowserClient钩子来检查用户是否已登录与否。useConvexAuth钩子确保浏览器获取了用于向Convex后端发送身份验证请求的认证令牌,并且该令牌已经被Convex后端验证。
    import { useConvexAuth } from "convex/react";
    function App() {
      const { isLoading, isAuthenticated } = useConvexAuth();
      return (
        <div className="App">
          {isAuthenticated ? "已登录状态" : "未登录或正在加载中"}
        </div>
      );
    }
全屏 开启 全屏 关闭
功能中的用户信息请参阅 函数中的认证 以了解如何访问已验证用户的信息,在查询、突变和操作中。
参考在Convex的数据库中存储用户,了解如何在数据库中存储用户信息。
Next.js 中的用户信息管理您可以从 Kinde 的 useKindeBrowserClient 或 getKindeServerSession 钩子函数获取经过身份验证的用户的名字和邮箱地址等信息。有关可用字段的列表,请参阅用户信息对象。
    "use client";
    import { useKindeBrowserClient } from "@kinde-oss/kinde-auth-nextjs";
    export default function Hero() {
      const { user } = useKindeBrowserClient();
      return <span>您是 {user?.given_name}, {user?.family_name}</span>;
    };
全屏 退出全屏
配置开发和生产环境让你的 Kinde 实例在 Convex 开发和生产部署之间有所不同,可以通过在 Convex 仪表盘上配置环境变量来实现这一点。
配置后端系统
Kinde 的默认配置设置为生产环境。若要使用自定义域名,请参阅此指南[在此处](https://docs.kinde.com/build/domains/pointing-your-domain/#:~:text=转到设置 > 环境 > 自定义域名,&text=在弹出窗口中,输入您的域名 <your_app>.kinde.com,然后点击保存)。
开发设置
打开你开发部署的设置,在Convex 仪表板中将你 .env.local 文件中的所有变量添加进去。
生产环境的设置
同样,在突出的仪表板上,在左侧菜单中切换到生产部署,并在你的 .env.local 文件里设置相应的变量。
现在运行命令 npx convex deploy 来切换到新的配置。
npx convex 部署命令进入全屏,退出全屏
发布你的 Next.js 应用
根据您所使用的托管平台,在生产环境中设置相应的环境变量。参见托管平台以获取更多信息。
调试身份验证如果用户成功完成了 Kinde 的注册或登录过程,并且在被保存到您的 Convex 数据库之后,页面会重定向回用户,但 useConvexAuth 却返回 isAuthenticated: false,那么您的后端可能没有正确配置。
你的 convex/ 目录下的 auth.config.ts 文件列出了已配置的身份验证提供程序。在添加新的提供程序后,你必须运行 npx convex dev 或 npx convex deploy 来将配置同步到后端服务器。
对于更详细的调试方法,请参阅身份验证调试。
技术细节实际上,认证流程内部是这样的。
- 用户点注册或登录按钮。
- 用户会被引导到 Kinde 的页面,在那里他们可以通过您设定的方法注册或登录。
- 接着,他们的信息将通过 webhook 发送到 Convex 并被安全存储,然后用户会被立即重定向回您的页面或您通过 Kinde 的 postLoginRedirectURL属性设置的其他页面。
- KindeProvider现在知道了用户已通过身份验证。
- useKindeAuth和- AuthTokenFetcher从 Kinde 获取一个授权令牌。
- 然后 react 的 useEffect钩子将其设置为 Convex 的setAuth实例。
- ConvexProvider将其传递给 Convex 后端验证。
- 您的 Convex 后端会从 Kinde 获取 domain、clientId 和 redirectUri,以验证令牌签名的有效。
- ConvexProvider收到身份验证成功的通知,现在您的整个应用程序都知道用户已通过 Convex 进行身份验证。- useConvexAuth返回- isAuthenticated: true,并且- Authenticated组件会渲染其子组件。
ConvexKindeProvider.tsx 文件中的设置负责在需要时刷新令牌,以确保用户始终与后端保持认证状态。
共同学习,写下你的评论
评论加载中...
作者其他优质文章
 
                 
            








 
			 
					 
					