Refine v5 实战:使用 Mantine 构建自定义认证系统(AuthProvider 完整指南)
2026/9/13 16:39:03 网站建设 项目流程

Refine v5 实战:使用 Mantine 构建自定义认证系统(AuthProvider 完整指南)

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

本文以 Refine 官方auth-mantine示例为主线,完整讲解如何基于 Mantine UI 与 Refine v5 从零实现一套可自定义的认证方案:包括 AuthProvider 全部方法的实现、<AuthPage>登录/注册/找回密码/更新密码四种页面、<Authenticated>路由守卫,以及 OAuth 第三方登录的接入方式。读完本文,你将掌握 Refine 认证机制的底层调用链,并能在自己的管理后台中直接落地这套方案。

一、认证在 Refine 中的工作方式

Refine 的认证能力完全由AuthProvider(认证提供器)承载。它是一个包含若干认证方法的普通对象,通过authProvider属性传给<Refine>组件后,这些方法会被 Refine 内部的认证 Hook(如useLoginuseIsAuthenticateduseGetIdentity等)自动消费:

import { Refine } from "@refinedev/core"; const App = () => { return <Refine authProvider={authProvider} />; };

两点关键认知(来自 AuthProvider 官方文档):

  • AuthProvider不是必需的。不传时应用没有认证能力,所有认证 Hook 与组件均不可用;
  • 所有方法期望返回Promise,因此天然支持异步逻辑(请求后端、读写 localStorage、刷新 Token 等),也可以接入 Auth0、Okta 等任意第三方认证服务,或完全自定义。

从 认证接口定义 的源码结构看,AuthProvider 分为必需方法loginchecklogoutonError)与可选方法registerforgotPasswordupdatePasswordgetPermissionsgetIdentity)两类。下面我们结合auth-mantine示例的完整实现逐一展开。

二、示例项目结构一览

示例位于 examples/auth-mantine,依赖组合为:@refinedev/core@refinedev/mantine@refinedev/react-router@refinedev/simple-rest,UI 层使用@mantine/core@mantine/form@mantine/notifications(见 package.json)。核心文件:

文件职责
examples/auth-mantine/src/App.tsxAuthProvider 实现 + 路由 + 认证页面装配
examples/auth-mantine/src/pages/posts/list.tsx受保护的资源页面
examples/auth-mantine/src/components/header/index.tsx顶部栏展示当前用户身份
cypress/e2e/auth-mantine/all.cy.ts认证全流程端到端测试

三、完整 AuthProvider 实现(Mock 凭据版)

示例在 examples/auth-mantine/src/App.tsx 中定义了一组模拟凭据,用于在没有真实后端的情况下演示完整认证闭环:

const authCredentials = { email: "demo@refine.dev", password: "demodemo", };

下面按方法逐一剖析。

3.1 login:登录与第三方 OAuth 跳转

login: async ({ providerName, email }) => { if (providerName === "google") { window.location.href = "https://accounts.google.com/o/oauth2/v2/auth"; return { success: true }; } if (providerName === "github") { window.location.href = "https://github.com/login/oauth/authorize"; return { success: true }; } if (email === authCredentials.email) { localStorage.setItem("email", email); return { success: true, redirectTo: "/" }; } return { success: false, error: { message: "Login failed", name: "Invalid email or password", }, }; },

要点解析:

  • login的入参来自useLogin触发 mutation 时传入的参数(示例中即providerNameemail),Refine 对参数类型不做任何约束,可自由定义;
  • OAuth 分支:当用户点击"Sign in with Google / GitHub"时,AuthProvider 直接通过window.location.href跳转到 OAuth 授权端点,返回success: true交由授权服务器接管后续流程;
  • 凭据校验分支:校验通过后把用户信息写入localStorage作为登录态,并通过redirectTo: "/"指定登录成功后的跳转路径;
  • 失败分支:返回success: falseerror对象(含messagename),Refine 会自动弹出错误通知——示例中的文案在测试里被断言为包含/invalid email/i(见 cypress/e2e/auth-mantine/all.cy.ts)。

login的返回类型为AuthActionResponse

type AuthActionResponse = { success: boolean; redirectTo?: string; error?: Error; [key: string]: unknown; };

3.2 check:登录态校验

check: async () => localStorage.getItem("email") ? { authenticated: true } : { authenticated: false, error: { message: "Check failed", name: "Not authenticated" }, logout: true, redirectTo: "/login", },

check是 Refine 判断"当前用户是否已认证"的入口,返回类型为CheckResponseauthenticated/redirectTo/logout/error)。用户导航到受保护页面时 Refine 会内部调用它:

  • email键 →authenticated: true,放行;
  • 无 →authenticated: false并携带redirectTo: "/login"logout: true,此时路由会带上to参数,登录成功后自动跳回原页面(详见"常见陷阱"一节)。

3.3 logout / onError:登出与错误兜底

logout: async () => { localStorage.removeItem("email"); return { success: true, redirectTo: "/login" }; }, onError: async (error) => { if (error.response?.status === 401) { return { logout: true }; } return { error }; },
  • logout清除登录态并跳转/login
  • onErroruseOnError消费,用于统一处理 API 错误:当后端返回401时返回logout: true,Refine 会调用logout自动登出——这是 JWT 过期场景的标准兜底做法。官方文档中给出的通用版本还会把403一并纳入,并支持redirectTo指定登出后去向(见 auth-provider/index.md)。

3.4 register / forgotPassword / updatePassword:完整认证闭环

register: async (params) => { if (params.email === authCredentials.email && params.password) { localStorage.setItem("email", params.email); return { success: true, redirectTo: "/" }; } return { success: false, error: { message: "Register failed", name: "Invalid email or password" }, }; }, forgotPassword: async (params) => { if (params.email === authCredentials.email) { // 在这里发送包含重置链接的邮件 return { success: true }; } return { success: false, error: { message: "Forgot password failed", name: "Invalid email" }, }; }, updatePassword: async (params) => { if (params.password === authCredentials.password) { // 在这里更新密码 return { success: true }; } return { success: false, error: { message: "Update password failed", name: "Invalid password" }, }; },

三个可选方法与login返回相同的AuthActionResponse类型,分别由useRegisteruseForgotPassworduseUpdatePasswordHook 消费。注册成功后同样写入localStorage并跳转首页,形成"注册 → 登录 → 修改密码"的完整用户旅程。

3.5 getPermissions / getIdentity:权限与身份

getPermissions: async () => ["admin"], getIdentity: async () => ({ id: 1, name: "Jane Doe", avatar: "https://unsplash.com/photos/IWLOvomUmWU/download?force=true&w=640", }),
  • getPermissions返回用户权限数组,可配合usePermissions做轻量级授权判断;官方文档提示:复杂授权场景建议改用 access control provider(见 authorization 文档);
  • getIdentity返回当前用户身份对象,供useGetIdentity消费。示例顶部栏正是用它渲染用户名与头像(见 components/header/index.tsx):
import { useGetIdentity } from "@refinedev/core"; const { data: user } = useGetIdentity<IUser>(); {(user?.name || user?.avatar) && ( <Group spacing="xs"> {user?.name && <Title order={6}>{user?.name}</Title>} <Avatar src={user?.avatar} alt={user?.name} radius="xl" /> </Group> )}

四、<AuthPage>:四种认证页面一键装配

<AuthPage>是 Refine 内置的认证页面组件(源码位于 packages/core/src/components/pages/auth/index.tsx),通过type属性切换四种形态:loginregisterforgotPasswordupdatePassword。在 Mantine 版本中它直接产出适配 Mantine 主题的表单。

示例中的登录页同时接入了 Google / GitHub 两个 OAuth 提供方:

<Route path="/login" element={ <AuthPage type="login" formProps={{ initialValues: { ...authCredentials, // 预填 demo@refine.dev / demodemo,方便演示 }, }} providers={[ { name: "google", label: "Sign in with Google", icon: <IconBrandGoogle />, }, { name: "github", label: "Sign in with GitHub", icon: <IconBrandGithub />, }, ]} /> } />

要点:

  • providers数组中的每一项都会渲染为一个第三方登录按钮,其name会作为providerName传入login方法——这正是上一节 OAuth 分支的触发来源;
  • 注册页同样传入providers(见 App.tsx 的 /register 路由),而/forgot-password/update-password仅使用默认表单。

五、<Authenticated>路由守卫与页面布局

认证状态如何转化为"路由拦截"?示例通过<Authenticated>组件包裹路由分组实现,它内部基于check方法的结果决定渲染子内容还是fallback

<Route element={ <Authenticated key="authenticated-routes" fallback={<CatchAllNavigate to="/login" />} > <ThemedLayout> <Outlet /> </ThemedLayout> </Authenticated> } > <Route index element={<NavigateToResource resource="posts" />} /> <Route path="/posts"> <Route index element={<PostList />} /> <Route path="create" element={<PostCreate />} /> <Route path="edit/:id" element={<PostEdit />} /> <Route path="show/:id" element={<PostShow />} /> </Route> </Route>

这段代码是"受保护区域"的典型写法:

  • 未登录访问/posts等受保护路由 →<CatchAllNavigate to="/login" />将用户重定向到登录页;
  • 已登录访问/login/register等认证页 → 反向拦截,<NavigateToResource resource="posts" />把用户送回首资源页;
  • 兜底路由(404)同样放在<Authenticated>包裹的<ThemedLayout>内,未登录用户不会看到错误页而是先去登录(见 App.tsx 末尾的 catch-all 路由)。

此外示例还启用了两个实用选项:

options={{ syncWithLocation: true, warnWhenUnsavedChanges: true, }}

分别用于 URL 与筛选/分页状态同步、以及表单未保存离开时的提醒(配合<UnsavedChangesNotifier />使用)。

六、从源码看认证 Hook 的调用链

结合 AuthProvider 官方文档 与示例实现,各方法的消费端对应关系如下:

AuthProvider 方法消费 Hook典型使用场景
loginuseLogin登录表单提交
checkuseIsAuthenticated路由守卫、页面加载时的认证判断
logoutuseLogout顶部栏退出按钮
onErroruseOnError全局 API 错误处理
registeruseRegister注册表单提交
forgotPassworduseForgotPassword忘记密码页
updatePassworduseUpdatePassword重置密码页
getPermissionsusePermissions按钮级权限控制
getIdentityuseGetIdentity顶部栏展示用户信息

需要特别强调的是redirectTo的语义:登录、注册、登出方法都可返回redirectTo指定跳转路径;若不需要跳转,返回redirectTo: undefined即可;错误信息则统一通过error: { name, message }定制,Refine 会在success: false时自动弹出通知(详见 auth-provider/index.md 的 FAQ 部分)。

七、端到端验证:Cypress 测试覆盖的行为契约

示例的认证逻辑并非孤立的演示代码,cypress/e2e/auth-mantine/all.cy.ts 中的端到端测试把关键行为固化为契约,值得逐条对照实现:

登录与重定向

it("should redirect to /login?to= if user not authenticated", () => { cy.visit("/test-route"); cy.get(".mantine-Title-root").contains(/sign in to your account/i); cy.location("search").should("contains", "to=%2Ftest"); cy.location("pathname").should("eq", "/login"); });

登出

it("should logout", () => { login(); cy.get("button").contains(/logout/i).click(); cy.location("pathname").should("eq", "/login"); });

身份展示

it("should render getIdentity response on header", () => { login(); cy.get(".mantine-Text-root").contains(/jane doe/i); cy.get(".mantine-Avatar-image").should("have.attr", "src"); });

测试共覆盖:登录成功、错误凭据提示、未登录重定向携带to参数、登录后回到原访问页、注册成功/失败、忘记密码失败、更新密码失败、登出、身份渲染九大场景。这意味着你在 examples/auth-mantine 中看到的每个方法都有对应的行为断言,可直接作为自定义认证逻辑的验收标准。

八、常见陷阱与实战建议

1.to参数与登录后回跳:未登录访问受保护页面时,Refine 会把原目标路径编码到/login?to=...login成功后自动跳回。自定义login实现时不要破坏这个链路,也不要在redirectTo里硬编码路径覆盖回跳逻辑。

2. 401 兜底必须接入:生产环境接入 JWT 时,务必实现onError并对 401/403 返回{ logout: true },否则 Token 过期后页面会停留在"假登录"状态。

3. OAuth 与本地态分离:示例中 OAuth 分支直接window.location.href跳转,登录态最终仍以localStorage为准。真实项目中应在 OAuth 回调页完成 code 换取 token,再写入存储,check保持一致。

4. AuthProvider 是纯接口:所有方法都是 async 函数,入参自由、返回类型固定。后端 API 形态(REST、GraphQL)不会影响本方案,只需在方法内部替换为真实请求即可。

5. 权限与身份尽量走 Hook:不要在组件里直接读localStorage,统一通过useGetIdentityusePermissions获取,便于未来切换到真实服务端鉴权时只改 AuthProvider。

九、运行示例

示例使用 Vite + Refine CLI 构建(package.json):

cd examples/auth-mantine pnpm install pnpm dev # 开发模式,Node >= 20 pnpm build # tsc && refine build

启动后访问/,使用预填的demo@refine.dev / demodemo即可体验完整认证流程;点击 Google / GitHub 按钮则触发 OAuth 跳转。示例与文档相互印证:完整的 AuthProvider 方法说明见 AuthProvider 官方文档,认证页面组件见 AuthPage 文档,各认证 Hook 的详细用法位于 authentication/hooks 目录。

【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询