十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

在 SpacetimeDB React 应用中集成 Clerk 身份认证:从 JWT 获取到连接鉴权的完整指南

在 SpacetimeDB React 应用中集成 Clerk 身份认证:从 JWT 获取到连接鉴权的完整指南 在 SpacetimeDB React 应用中集成 Clerk 身份认证从 JWT 获取到连接鉴权的完整指南【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB本文以 SpacetimeDB 官方文档中的 Clerk 集成指南为核心完整演示如何在 React 客户端中接入 Clerk获取会话 JWT并通过DbConnection.builder().withToken(...)将其作为身份凭据传递给 SpacetimeDB。读完本文你将掌握 ClerkProvider 与自定义 TokenProvider 的组合写法、服务端 JWT 校验含 OIDC/JWKS 发现机制的底层原理以及通过 JWT 模板约束 issuer 与 audience 的进阶安全实践。前置条件开始之前请确认以下环境已经就绪一个可运行的 SpacetimeDB 项目。如果你还没有搭建好可以参照 React 快速入门指南运行spacetime dev --template react-ts即可创建包含服务端模块与 React 客户端、并自动生成 TypeScript 绑定的完整项目骨架。一个 Clerk 账户以及在该账户下创建的应用程序Application。整个集成思路与仓库中的 Auth0 集成文档 一脉相承由第三方身份提供商Clerk负责登录与发证SpacetimeDB 只负责验证令牌、识别身份——它并不关心 JWT 是哪家签发只要能够通过签名与 issuer 校验即可。第一步安装 Clerk React SDK在你的 React 应用中安装clerk/clerk-react官方支持 NPM、Yarn、PNPM、Bun 四种包管理器npm add clerk/clerk-reactyarn add clerk/clerk-reactpnpm add clerk/clerk-reactbun add clerk/clerk-react第二步配置 Clerk 应用进入 Clerk Dashboard完成以下配置创建或选择一个 Application。在应用设置中找到Publishable key发布密钥保存备用——它将在稍后的main.tsx中使用。在 Clerk 的允许域名列表中登记你的本地开发地址。若使用 Vite默认开发地址为http://localhost:5173务必将其加入允许列表否则登录跳转会被拦截。确认可以获得 JWTClerk 通过 Session Token 形式签发 JWT后续我们会在浏览器端从当前会话中取回该令牌并发送给 SpacetimeDB。第三步创建 ClerkTokenProvider 组件在项目根目录与App.tsx同级新建ClerkTokenProvider.tsx。该组件承担三项职责确保用户已登录——未登录时渲染 Clerk 的RedirectToSignIn组件把用户导向 Clerk 的登录界面登录成功后通过getToken()从会话中取回 JWT将令牌通过 React Context 暴露给应用中的任意组件其模式与 Auth0 教程中的AutoLogin组件一致。import React, { createContext, useContext, useEffect, useMemo, useState, } from react; import { useAuth, RedirectToSignIn } from clerk/clerk-react; const TokenContext createContextstring | undefined(undefined); export function useClerkToken() { const token useContext(TokenContext); if (!token) { throw new Error(useClerkToken must be used within a ClerkTokenProvider); } return token; } /** * ClerkTokenProvider: * - If signed out: renders Clerks redirect component. * - If signed in: loads a Clerk session token (JWT) and provides it via context. * * Note: * - getToken() returns a token suitable for sending to your backend. If you have * configured a specific JWT template in Clerk, pass its name via * getToken({ template: YOUR_TEMPLATE_NAME }). */ export function ClerkTokenProvider({ children, }: { children: React.ReactNode; }) { const { isLoaded, isSignedIn, getToken } useAuth(); const [token, setToken] useStatestring | null(null); const [error, setError] useStateError | null(null); useEffect(() { let cancelled false; async function run() { if (!isLoaded) return; // IMPORTANT: if signed out, clear any cached token if (!isSignedIn) { if (!cancelled) setToken(null); return; } try { // If you use a Clerk JWT template, use: // const t await getToken({ template: YOUR_TEMPLATE_NAME }); const t await getToken(); if (!t) { throw new Error(Clerk returned no session token.); } if (!cancelled) setToken(t); } catch (e) { if (!cancelled) setError(e as Error); } } run(); return () { cancelled true; }; }, [isLoaded, isSignedIn, getToken]); const value useMemostring | undefined(() token ?? undefined, [token]); if (error) { return ( div pAuthentication error/p pre{error.message}/pre /div ); } if (!isLoaded) { return pLoading.../p; } if (!isSignedIn) { // Sends the user to Clerk sign-in. After sign-in, they return to the app. return RedirectToSignIn /; } if (!token) { return pLoading.../p; } return ( TokenContext.Provider value{value}{children}/TokenContext.Provider ); }实现中有几个值得留意的细节加载态分级isLoaded、isSignedIn、token三个状态分别渲染Loading...、跳转登录、继续等待令牌避免在令牌就绪前就渲染业务组件导致空值穿透登出清空令牌!isSignedIn分支会显式将本地 token 置为null防止用户登出后旧令牌仍被 Context 缓存并被复用竞态保护useEffect中使用cancelled标志组件卸载后不再执行setToken避免 React 警告与状态泄漏。第四步在 main.tsx 中用 ClerkProvider 包裹应用编辑main.tsx在最外层包裹ClerkProvider并把publishableKey设为第二步保存的发布密钥内层再套上我们自定义的ClerkTokenProviderimport { StrictMode } from react; import { createRoot } from react-dom/client; import { ClerkProvider } from clerk/clerk-react; import App from ./App.tsx; import { ClerkTokenProvider } from ./ClerkTokenProvider.tsx; createRoot(document.getElementById(root)!).render( StrictMode ClerkProvider publishableKeyYOUR_CLERK_PUBLISHABLE_KEY ClerkTokenProvider App / /ClerkTokenProvider /ClerkProvider /StrictMode );组件层级为ClerkProvider → ClerkTokenProvider → App顺序不可颠倒ClerkTokenProvider内部调用的useAuth()必须处于ClerkProvider的 Context 作用域之内才能工作。第五步在 App.tsx 中使用 Clerk 令牌建立连接修改App.tsx通过useClerkToken()读取令牌并将其传入DbConnection.builder()的.withToken(...)import { useMemo } from react; import { Identity } from spacetimedb; import { SpacetimeDBProvider } from spacetimedb/react; import { DbConnection, ErrorContext } from ./module_bindings; import { useClerkToken } from ./ClerkTokenProvider; const onConnect (_conn: DbConnection, identity: Identity) { console.log( Connected to SpacetimeDB with identity:, identity.toHexString() ); }; const onDisconnect () { console.log(Disconnected from SpacetimeDB); }; const onConnectError (_ctx: ErrorContext, err: Error) { console.log(Error connecting to SpacetimeDB:, err); }; export default function App() { const token useClerkToken(); const connectionBuilder useMemo(() { return DbConnection.builder() .withUri(YOUR SPACETIMEDB URL) .withModuleName(YOUR SPACETIMEDB MODULE NAME) .withToken(token) .onConnect(onConnect) .onDisconnect(onDisconnect) .onConnectError(onConnectError); }, [token]); return ( SpacetimeDBProvider connectionBuilder{connectionBuilder} div h1SpacetimeDB React App/h1 p You can now use SpacetimeDB in your app with Clerk authentication! /p /div /SpacetimeDBProvider ); }注意connectionBuilder被useMemo包裹、依赖项为token当 Clerk 令牌刷新比如会话续期时builder 会携带新令牌重建SpacetimeDBProvider会依据新的 builder 重建连接。onConnect回调中打印的identity.toHexString()是 SpacetimeDB 根据 JWT 计算出的用户唯一身份标识可用于后续的用户数据关联。第六步可选添加登录态 UI 与登出入口Clerk 提供了开箱即用的UserButton组件可直接放入页面头部实现用户菜单与登出功能import { UserButton } from clerk/clerk-react; export function Header() { return ( header style{{ display: flex, justifyContent: flex-end, padding: 12 }} UserButton / /header ); }进阶JWT 模板与 claim 控制默认情况下getToken()返回的 Session Token 即可满足 SpacetimeDB 的身份校验。但官方强烈建议使用Clerk JWT 模板JWT Templates来控制令牌的claims、audience与issuer。启用模板后只需修改令牌获取一行await getToken({ template: YOUR_TEMPLATE_NAME });同时必须保证你的 SpacetimeDB 认证层能够校验对应的 issuer 与签名密钥详见下文源码原理。原理纵深JWT 在 SpacetimeDB 中的校验链路客户端侧令牌如何进入连接.withToken(token)是 TypeScript SDK 中DbConnectionBuilder的链式方法之一db_connection_builder.ts其文档说明该参数为用于向 SpacetimeDB 认证的凭据可选不传则走匿名连接。在 db_connection_impl.ts 中令牌会作为authToken随握手消息发送给服务端。连接建立后若服务端返回了新的令牌SDK 会将其保存在连接对象上并在后续的onConnect回调中回传给应用db_connection_impl.ts。SpacetimeDBProviderSpacetimeDBProvider.ts接收connectionBuilder通过ConnectionManager以(uri, moduleName)为键对连接进行管理与复用。服务端侧两级验证器SpacetimeDB 核心服务端对传入 JWT 的校验实现在 token_validation.rs。默认使用FullTokenValidator其策略是先本地、后 OIDC两级回退token_validation.rs本地密钥验证先用 SpacetimeDB 自身密钥验签对应 SpacetimeAuth/内部签发的令牌OIDC 发现验证本地验签失败后从 JWT 中无签名地读取issget_raw_issuer见 token_validation.rs拼接出{issuer}/.well-known/openid-configuration发现端点获取jwks_uri再拉取 JWKS 公钥集合对令牌验签token_validation.rs。Clerk 的 JWT 正是走第二条路径Clerk 公开其 OpenID 配置与 JWKS 端点SpacetimeDB 依据iss自动发现公钥并完成签名校验无需在服务端手工配置 Clerk 公钥。签名算法与必填声明验签时token_validation.rs对算法族有明确约束EC 密钥只接受ES256RSA 密钥只接受RS256HMAC 只接受HS256并强制要求sub与iss两个必填声明。Clerk 的 Session Token 默认即包含sub用户在 Clerk 中的唯一标识与issClerk 的签发者地址因此天然满足要求。过期时间exp在解码后单独校验保留 60 秒的宽限期。值得注意的是目前服务端默认不校验aud声明源码中validation.validate_aud false并留有 We should require a specific audience at some point 的 TODO。这意味着任何持有合法签名令牌的客户端都能连接令牌是否发给你这个应用需要由应用层自行把关——这正是官方文档在 Auth Claims 使用指南 中反复强调的要点。服务端如何读取 claim 做授权在服务端模块中可以通过ReducerContext的sender_auth/SenderAuth读取 JWT claim。以客户端连接事件为例Rust#[reducer(client_connected)] pub fn connect(ctx: ReducerContext) - Result(), String { let auth_ctx ctx.sender_auth(); let (subject, issuer) match auth_ctx.jwt() { Some(claims) (claims.subject().to_string(), claims.issuer().to_string()), None { return Err(Client connected without JWT.to_string()); } }; log::info!(sub: {}, iss: {}, subject, issuer); Ok(()) }由于 SpacetimeDB 接受任何能通过签名校验的 OIDC 令牌仅凭iss区分来源是不够的还应该校验aud。官方推荐在客户端连接时至少检查 issuer 与 audience确保你的数据只能被你的应用用户访问同时防止其他应用拿发给它们的令牌来冒充你的用户。参考模式Rust// Set this to the OIDC client (or set of clients) set up for your project. const OIDC_CLIENT_ID: str client_XXXXXXXXXXXXXXXXXXXXXX; #[reducer(client_connected)] pub fn connect(ctx: ReducerContext) - Result(), String { let jwt ctx.sender_auth().jwt().ok_or(Authentication required.to_string())?; if jwt.issuer() ! https://auth.spacetimedb.com/oidc { return Err(Invalid issuer.to_string()); } if !jwt.audience().iter().any(|a| a OIDC_CLIENT_ID) { return Err(Invalid audience.to_string()); } Ok(()) }对应到 Clerk 场景在 Clerk JWT 模板中显式设置audience通常设为你的应用标识并在服务端client_connectedreducer 中校验issClerk 的签发地址与aud即可构建出只接受本应用用户的完整防线。最佳实践小结令牌生命周期JWT 过期后应刷新——将getToken()放入依赖isSignedIn/会话刷新的逻辑中或让ClerkTokenProvider随会话状态重新取令牌从而触发connectionBuilder重建使用 JWT 模板优先通过 Clerk JWT 模板固定issuer、audience与自定义 claims而不是依赖默认令牌服务端双校验在client_connected事件中校验iss与aud因为当前服务端默认不校验 audience这是防止令牌被跨应用滥用的关键自定义 claims 授权需要角色等自定义声明时在服务端反序列化 JWT 完整 payload 后校验详见 Auth Claims 使用指南 中Accessing custom claims一节的 TS/C#/Rust 三种实现。完成上述步骤后你的 SpacetimeDB React 应用即具备完整的 Clerk 登录闭环用户访问应用 → 跳转 Clerk 登录 → 浏览器内取得会话 JWT → 作为 bearer token 随连接发送 → 服务端通过 OIDC 发现完成验签与身份计算 → 以Identity为单位提供数据服务。【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表