
SpacetimeDB 集成 Auth0 认证指南React 客户端登录与 JWT 服务端校验【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB导读本文以 SpacetimeDB 官方文档中的 Auth0 集成指南为骨架完整讲解如何为 SpacetimeDB 的 React 前端应用接入 Auth0 第三方 OIDC 认证从安装auth0/auth0-reactSDK、在 Auth0 Dashboard 创建单页应用到编写AutoLogin组件自动获取 ID Token再通过DbConnection.builder().withToken(...)把 token 交给 SpacetimeDB 连接层。文章同时结合仓库源码crates/auth/src/identity.rs、sdks/typescript/src/sdk/db_connection_builder.ts与官方服务端文档docs/docs/00200-core-concepts/00500-authentication/00500-usage.md深入讲解 JWT 如何映射为 SpacetimeDBIdentity、服务端 reducer 中如何读取与校验sub/iss/aud及自定义 claims帮助你完成客户端登录 服务端鉴权的完整闭环。前置条件开始之前请确认以下条件已具备一个可运行的 SpacetimeDB 项目。若还没有请先按照 React Quickstart Guide 完成项目初始化。一个可用的 Auth0 账号并能访问 Auth0 Dashboard 创建应用。理解 OIDC/JWT 基本概念subsubject用户唯一标识、ississuer令牌签发方、audaudience令牌接收方、ID Tokenid_token。SpacetimeDB 采用 OIDC 兼容的 JWT 作为认证凭据因此 Auth0 签发的 ID Token 可直接复用。阅读提示仓库根目录下还有 00100-spacetimeauth 系列文档介绍 SpacetimeDB 自带的认证方案本文聚焦第三方 Auth0 接入两者可以互为补充。第一步安装 Auth0 React SDK在 React 应用根目录执行以下任一命令安装auth0/auth0-react按你的包管理器选择npm add auth0/auth0-reactyarn add auth0/auth0-reactpnpm add auth0/auth0-reactbun add auth0/auth0-reactauth0/auth0-react提供了Auth0Provider全局认证上下文和useAuth0Hook暴露loginWithRedirect、getIdTokenClaims、isAuthenticated、isLoading等 API是本文方案的基础。第二步在 Auth0 Dashboard 创建单页应用登录 Auth0 Dashboard进入ApplicationsApplications。点击Create Application。在弹出的对话框中为应用命名应用类型选择Single Page Web Application点击Create。进入Application Details页面的Settings标签页。记录下Domain与Client ID两个值稍后配置Auth0Provider与withToken时会用到。在同一页面的 Settings 标签中按下表配置 URLlocalhost:5173为 Vite 开发服务器默认端口若你的应用跑在其他端口请自行替换URL TypeURLAllowed Callback URLshttp://localhost:5173Allowed Logout URLshttp://localhost:5173Allowed Web Originshttp://localhost:5173为什么必须配置这三个 URLAllowed Callback URLsAuth0 完成登录后重定向回前端的地址必须与Auth0Provider中authorizationParams.redirect_uri一致。Allowed Logout URLsAuth0 退出登录后的跳转地址。Allowed Web Origins允许从前端发起认证请求的源Origin未登记的来源会被 Auth0 拒绝。配置保存后Auth0 即可向你的 SPA 签发 ID Token。ID Token 是一个符合 OIDC 规范的 JWT其中包含sub、iss、aud等标准 claims并可用https://jwt.io之类的工具查看载荷内容。第三步创建 AutoLogin 组件创建一个AutoLogin组件它负责两件事自动重定向未登录用户到 Auth0 登录页以及获取 ID Token 并通过 React Context 提供给子组件。完整代码如下import { useAuth0 } from auth0/auth0-react; import { createContext, useContext, useEffect, useMemo, useState } from react; const IdTokenContext createContextstring | undefined(undefined); export function useIdToken() { const ctx useContext(IdTokenContext); if (!ctx) { throw new Error(useIdToken must be used within an IdTokenProvider); } return ctx; } export function AutoLogin({ children }: { children: React.ReactNode }) { const { isLoading, isAuthenticated, loginWithRedirect, getIdTokenClaims } useAuth0(); const [idToken, setIdToken] useStatestring | null(null); const [error, setError] useStateError | null(null); useEffect(() { if (!isLoading !isAuthenticated) { loginWithRedirect().catch(err setError(err)); } }, [isLoading, isAuthenticated, loginWithRedirect]); useEffect(() { let cancelled false; const run async () { if (isLoading) return; // IMPORTANT: If not authenticated, ensure token is cleared if (!isAuthenticated) { if (!cancelled) setIdToken(null); return; } try { const claims await getIdTokenClaims(); const token claims?.__raw ?? null; if (!token) { throw new Error(Auth0 returned no ID token (__raw missing).); } if (!cancelled) { setIdToken(token); } } catch (e) { if (!cancelled) setError(e as Error); } }; run(); return () { cancelled true; }; }, [isLoading, isAuthenticated, getIdTokenClaims]); const value useMemostring | undefined(() { return idToken ?? undefined; }, [idToken]); const ready !isLoading isAuthenticated !!idToken !error; if (error) { return ( div pAuthentication error/p pre{error.message}/pre /div ); } if (!ready) { return pLoading.../p; } return ( IdTokenContext.Provider value{value}{children}/IdTokenContext.Provider ); }实现要点解读自动重定向第一个useEffect监听isLoading/isAuthenticated当加载完成且未登录时调用loginWithRedirect()跳转 Auth0 登录页登录成功后 Auth0 会重定向回redirect_uri并携带授权码SDK 内部完成令牌交换。获取原始 JWTgetIdTokenClaims()返回解码后的 claims 对象其中__raw字段保存了未经解码的原始 ID Token 字符串——这正是后续要传给 SpacetimeDB 的凭据。代码显式检查__raw缺失并抛错避免把空值传入连接层。竞态防护run()中使用cancelled标志位在组件卸载时置位防止异步回调在卸载后写入 state 导致 React 告警或内存泄漏。派生状态ready由!isLoading isAuthenticated !!idToken !error四条件共同决定只有 token 真正就绪后才渲染子组件保证useIdToken()的返回值在子组件中始终有效。错误处理error存在时直接渲染错误信息与error.message方便定位登录/取 token 失败的原因。第四步在 main.tsx 中挂载 Auth0Provider编辑main.tsx用Auth0Provider包裹整个应用并把AutoLogin放在其内部import { Auth0Provider } from auth0/auth0-react; import { StrictMode } from react; import { createRoot } from react-dom/client; import App from ./App.tsx; import { AutoLogin } from ./AutoLogin.tsx; createRoot(document.getElementById(root)!).render( StrictMode Auth0Provider domainYOUR_AUTH0_DOMAIN clientIdYOUR_AUTH0_CLIENT_ID authorizationParams{{ redirect_uri: window.location.origin, }} AutoLogin App / /AutoLogin /Auth0Provider /StrictMode );参数说明domain第二步在 Auth0 Dashboard 保存的Domain如your-app.us.auth0.com。clientId保存的Client ID。authorizationParams.redirect_uriAuth0 登录后的回调地址这里动态取window.location.origin开发环境即http://localhost:5173必须与 Auth0 应用中配置的Allowed Callback URLs保持一致。注意如果main.tsx里已存在SpacetimeDBProvider包裹代码请先移除因为本文方案中SpacetimeDBProvider会在下一步的App.tsx内部、且需要先拿到 ID Token 之后再挂载。第五步在 App.tsx 中使用 ID Token 构建连接修改App.tsx通过useIdToken()获取 token并传入DbConnection.builder()import { useMemo } from react; import { Identity } from spacetimedb; import { SpacetimeDBProvider } from spacetimedb/react; import { useIdToken } from ./AutoLogin; import { DbConnection, ErrorContext } from ./module_bindings; 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 idToken useIdToken(); const connectionBuilder useMemo(() { return DbConnection.builder() .withUri(YOUR SPACETIMEDB URL) .withDatabaseName(YOUR SPACETIMEDB MODULE NAME) .withToken(idToken) .onConnect(onConnect) .onDisconnect(onDisconnect) .onConnectError(onConnectError); }, [idToken]); return ( SpacetimeDBProvider connectionBuilder{connectionBuilder} div h1SpacetimeDB React App/h1 pYou can now use SpacetimeDB in your app!/p /div /SpacetimeDBProvider ); }withToken 在 SDK 中的实现withToken是DbConnectionBuilder的链式方法见 sdks/typescript/src/sdk/db_connection_builder.ts/** * Set the identity of the client to connect to the database. * * param token The credentials to use to authenticate with SpacetimeDB. This * is optional. You can store the token returned by the onConnect callback * to use in future connections. * * returns The DbConnectionBuilder instance. */ withToken(token?: string): this { this.#token token; return this; }它把 token 存入 builder 私有字段后续build()建立 WebSocket 连接时会在握手消息中携带该 token由服务端完成签名验证与 claims 解析。SDK 中 connection_manager.ts 在恢复已有连接时也会调用builder.withToken(managed.state.token)说明 token 同时用于首次连接鉴权与断线重连恢复身份。注意useMemo(..., [idToken])connectionBuilder依赖idToken重建。当用户登出导致idToken变为undefined时builder 会以无 token 状态重建这与AutoLogin中未认证即清空 token的行为相呼应。服务端视角JWT 如何变成 Identity客户端把 Auth0 ID Token 交给连接层后服务端会解析并校验它。核心逻辑位于 crates/auth/src/identity.rs。服务端首先把 JWT 载荷反序列化为IncomingClaimscrates/auth/src/identity.rs其关键字段包括sub签发方分配给用户的唯一标识Auth0 中即用户 IDiss签发方Auth0 的 Domainaud接收方标识Auth0 场景下为应用的 Client ID支持单个字符串或字符串数组两种形式源码中用 untagged 枚举兼容解析见 crates/auth/src/identity.rsiat/exp签发时间与过期时间extra其余所有自定义 claims通过#[serde(flatten)]捕获。随后进行合法性校验crates/auth/src/identity.rsiss与sub必须非空且长度不超过 128 字节Identity 计算Identity::from_claims(issuer, subject)由isssub哈希派生用户的 SpacetimeDBIdentity——这意味着只要使用同一 Auth0 租户签发同一用户的 token其 SpacetimeDB Identity 就稳定不变若 token 中携带identity字段则必须与计算出的 Identity 一致防止身份伪造。这也解释了onConnect回调中打印的identity.toHexString()与 Auth0 用户的一一对应关系。服务端鉴权在 reducer 中读取与校验 claims客户端连接携带 JWT 后服务端 reducer 中可以通过ReducerContext的senderAuth即AuthCtx访问 claims。以下能力来自 docs/docs/00200-core-concepts/00500-authentication/00500-usage.md 及对应 SDK 实现。读取标准 claimssubject 与 issuersub和iss是最常用的两个 claimSDK 提供了辅助方法。以 TypeScript 服务端为例import { SenderError } from spacetimedb/server; export const onConnect spacetimedb.clientConnected(ctx { const jwt ctx.senderAuth.jwt; if (jwt null) { throw new SenderError(Unauthorized: JWT is required to connect); } console.info(Client connected with sub: ${jwt.subject}, iss: ${jwt.issuer}); });Rust 服务端对应写法源码位于 crates/.../00500-usage.md 文档示例#[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(()) }使用 Google 签发 token 时的输出示例INFO: src\lib.rs:64: sub: 321321321321321, iss: https://accounts.google.com限制签发方issuer与受众audience任何有效的 OIDC token 都能连接到 SpacetimeDB因此必须校验iss与aud防止用户用其他签发方如 GitHub的 token 访问你的模块数据也防止第三方把为别的应用签发的 token 重放给你。官方推荐在client_connected时至少校验 issuer并务必校验aud。以 SpacetimeAuth 为例TS 版const OIDC_CLIENT_IDS [client_XXXXXXXXXXXXXXXXXXXXXX]; export const onConnect spacetimedb.clientConnected(ctx { const jwt ctx.senderAuth.jwt; if (jwt null) { throw new SenderError(Unauthorized: JWT is required to connect); } if (jwt.issuer ! https://auth.spacetimedb.com/oidc) { throw new SenderError(Unauthorized: Invalid issuer ${jwt.issuer}); } if (!jwt.audience.some(aud OIDC_CLIENT_IDS.includes(aud))) { throw new SenderError(Unauthorized: Invalid audience ${jwt.audience}); } });对应到 Auth0 场景把jwt.issuer与你的 Auth0 Domain 比对格式如https://your-app.us.auth0.com/把jwt.audience与你的 Auth0Client ID比对即可。Rust 版实现方式一致jwt.issuer()与jwt.audience().iter().any(...)C# 与 C 版本可参见 00500-usage.md 的对应 Tab。读取自定义 claims如 rolesSDK 只解析标准 claims其余字段可通过完整载荷访问。假设 token 携带roles数组要求只有admin角色可调用某 reducerimport { SenderError, type InferSchema, type ReducerCtx } from spacetimedb/server; type Ctx ReducerCtxInferSchematypeof spacetimedb; function ensureAdminAccess(ctx: Ctx) { const auth ctx.senderAuth; if (auth.isInternal) { return; // 定时 reducer 等内部调用视为可信 } const jwt auth.jwt; if (jwt null) { throw new SenderError(Unauthorized: JWT is required); } const roles jwt.fullPayload[roles]; if (!Array.isArray(roles) || !roles.includes(admin)) { throw new SenderError(Unauthorized: Admin role is required); } } export const adminonly spacetimedb.reducer(ctx { ensureAdminAccess(ctx); });Rust 版本需要先解析 JSON 载荷在Cargo.toml中引入serde与serde_json[dependencies] serde { version 1.0.219, features [derive] } serde_json 1.0.143#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct CustomClaims { pub roles: VecString, } fn ensure_admin_access(sender_auth: spacetimedb::AuthCtx) - Result(), String { if sender_auth.is_internal() { return Ok(()); // 定时 reducer 已可信 } let jwt sender_auth.jwt().ok_or(Authentication required.to_string())?; let claims: CustomClaims serde_json::from_slice(jwt.raw_payload().as_bytes()) .map_err(|e| format!(Client connected with invalid JWT: {}, e))?; if claims.roles.iter().any(|r| r admin) { return Ok(()); } Err(Admin role required.to_string()) } #[spacetimedb::reducer] pub fn admin_only_reducer(ctx: ReducerContext) - Result(), String { ensure_admin_access(ctx.sender_auth())?; Ok(()) }在 Auth0 中自定义 claims 需要配置Actions登录后执行的自定义脚本把角色等字段写入 ID Token然后在 SpacetimeDB 服务端按上述方式读取。isInternal/is_internal()判断用于区分定时调度等内部触发与外部客户端调用内部调用默认放行。总结与最佳实践连接前鉴权在client_connectedreducer 中始终校验 JWT 是否存在并核对iss签发方与aud接收方后再放行避免数据被无关签发方或其他应用的 token 访问。自定义 claimsSDK 仅默认解析标准 claims应用级逻辑如角色、权限需要反序列化完整 JWT 载荷fullPayload/raw_payload后使用。Identity 稳定性SpacetimeDB 的Identity由isssub派生见 crates/auth/src/identity.rs因此用户身份与签发方绑定切换签发方会得到不同的 Identity。token 生命周期ID Token 有有效期exp过期后需重新走 Auth0 登录流程获取新 tokenSDK 的withToken支持存储并复用onConnect返回的身份凭据见 db_connection_builder.ts。前端与后端配套AutoLogin负责保证拿到有效 ID Token 才渲染应用SpacetimeDBProvider依赖该 token 构建连接两者配合才能让每次连接都携带可验证的身份凭据。至此你已经完成了Auth0 登录 → ID Token 注入 SpacetimeDB 连接 → 服务端 claims 校验的完整认证闭环。若想进一步了解 SpacetimeDB 自带的认证方案可继续阅读 00100-spacetimeauth 系列文档完整的 claims 使用示例与各语言TS/C#/Rust/C对照见 Using Auth Claims。【免费下载链接】SpacetimeDBDevelopment at the speed of light项目地址: https://gitcode.com/GitHub_Trending/sp/SpacetimeDB创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考