新闻详情

新闻详情

首页 / 资讯中心 / 详情

urql 认证实战指南:用 @urql/exchange-auth 实现 JWT 登录、令牌刷新与登出

发布时间:2026/9/25 5:57:01来源:尧图网络
urql 认证实战指南:用 @urql/exchange-auth 实现 JWT 登录、令牌刷新与登出
前端【免费下载链接】urqlThe highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.项目地址https://gitcode.com/gh_mirrors/ur/urql点击查看免费下载大多数 GraphQL API 都带有某种形式的认证最常见的是在每个请求头中携带一个认证令牌auth token。本指南基于 urql 官方文档中的《Authentication》章节系统讲解如何借助urql/exchange-auth提供的authExchange在 urql 客户端中落地完整的 JWT 认证流程——包括首次登录、会话恢复、令牌过期后的静默刷新、强制登出与用户主动登出并深入其源码实现与仓库内的可运行示例帮助读者掌握一套可直接复制的生产级认证方案。读完本文你将能够正确地把authExchange接入 urqlClient的 exchanges 链配置addAuthToOperation、didAuthError、refreshAuth、willAuthError四个核心回调识别 GraphQL 错误扩展码如FORBIDDEN/UNAUTHORIZED与 HTTP 401 两种认证失败信号并处理好登出后的缓存失效问题。典型的认证流程authExchange的设计目标是覆盖 JWT 认证场景中几个反复出现的状态流转阶段。理解这些阶段是正确配置各项回调的前提首次登录Initial login——用户首次打开应用并完成认证获得认证令牌。令牌需要被保存到可跨会话持久化的存储中例如 Web 端的localStorage、React Native 的AsyncStorage并随后附加到每个请求的认证头中。会话恢复Resume——用户此前已认证过再次打开应用时从持久化存储中读取令牌并附加到每个请求通常作为 auth header。令牌失效导致的强制登出Forced log out——会话可能因多种原因失效令牌过期、用户在其他设备退出、服务端远程作废会话等。此时应用应当同步登出用户清除持久化存储并重定向到首页或登录页。用户主动登出User initiated log out——用户选择退出时通常先向 API 发送登出请求然后清除持久化存储中的令牌最后重定向到首页或登录页。令牌刷新Refresh可选——并非所有 API 都支持。若支持用户会同时获得一个有效期较短的认证令牌如 1 周和一个有效期较长的刷新令牌如 6 个月。刷新令牌可用于在认证令牌过期后换取新的认证令牌。刷新触发时机有两种一是解码 JWT 发现其已过期二是 API 请求返回未授权响应——对 GraphQL API 而言通常是错误码而不是 HTTP 401但两种都可以支持。刷新成功后可通过 GraphQL mutation 或独立 REST 端点实现将新令牌写回持久化存储并用新 auth header 重试之前失败的请求。若刷新失败或携带新令牌重试的请求第二次仍返回认证错误则应当登出用户并清除持久化存储。仓库中的可运行示例 examples/with-refresh-auth 完整演示了上述流程登录成功后通过 authStore.js 把token与refresh_token写入localStorage应用重启时在App.jsx中依据getToken()判断登录态从而实现会话恢复。安装与接入 Client首先安装urql/exchange-auth需要与urql一同安装yarn add urql/exchange-auth # or npm install --save urql/exchange-auth然后将包导出的authExchange加入Client的 exchanges 链。authExchange是一个异步 exchange必须放在所有fetchExchange之前同时放在所有同步 exchange如cacheExchange之后import { Client, cacheExchange, fetchExchange } from urql; import { authExchange } from urql/exchange-auth; const client new Client({ url: http://localhost:3000/graphql, exchanges: [ cacheExchange, authExchange(async utils { return { /* config... */ }; }), fetchExchange, ], });authExchange接收一个初始化函数该函数在 exchange 首次初始化时被调用。它会向你传递一个工具utilities对象而你必须返回一个Promise 包裹的配置对象。从源码看这一契约被严格定义为init: (utilities: AuthUtilities) PromiseAuthConfig见 exchanges/auth/src/authExchange.ts#L198-L200初始化函数返回的配置类型AuthConfig包含以下成员authExchange.ts#L67-L138addAuthToOperation必填告诉authExchange如何把认证信息附加到 Operation 上例如把认证状态写进操作的fetchOptions.headers。willAuthError可选在请求发出前被调用用于检测令牌是否过期、预判请求是否会因认证失败。didAuthError可选在收到 API 结果后用于判断错误是否为认证错误。refreshAuth当认证错误发生时被调用给你更新认证状态的机会调用完成后authExchange会重试该 Operation。配置初始化函数启动时读取认证状态初始化函数必须返回配置对象的 Promise因此它天然提供了一个异步读取认证状态的机会。Web 端可以从localStorage同步读取async function initializeAuthState() { const token localStorage.getItem(token); const refreshToken localStorage.getItem(refreshToken); return { token, refreshToken }; } authExchange(async utils { let { token, refreshToken } initializeAuthState(); return { /* config... */ }; });在 React Native 中持久化存储如AsyncStorage总是异步且 Promise 化的因此需要await读取令牌。由于authExchange的初始化函数本身是 async即必须返回Promise这种方式同样成立async function initializeAuthState() { const token await AsyncStorage.getItem(TOKEN_KEY); const refreshToken await AsyncStorage.getItem(REFRESH_KEY); return { token, refreshToken }; } authExchange(async utils { let { token, refreshToken } initializeAuthState(); return { /* config... */ }; });从源码实现看初始化流程由initAuth函数驱动authExchange.ts#L228-L298exchange 在收到操作流时会先调用initAuth()若初始化 Promise reject会以makeErrorResult把错误直接转给所有排队中的操作并阻止后续操作继续转发直到初始化成功authExchange.test.ts#L480-L508 中的测试用例也验证了初始化失败时错误会原样透传给结果。这意味着初始化函数里应自行 try/catch 异常存储读取避免初始化中断。配置addAuthToOperation把令牌附加到每次请求addAuthToOperation的作用是把认证状态应用到每个请求。在此我们使用传入的appendHeaders工具——它是快速通过fetchOptions给Operation添加 HTTP 头的便捷方法当然你也可以用makeOperation直接编辑Operation的上下文authExchange(async utils { let token await AsyncStorage.getItem(TOKEN_KEY); let refreshToken await AsyncStorage.getItem(REFRESH_KEY); return { addAuthToOperation(operation) { if (!token) return operation; return utils.appendHeaders(operation, { Authorization: Bearer ${token}, }); }, // ... }; });首先检查token非空然后用appendHeaders以Authorization头的形式应用到请求上。appendHeaders的底层实现authExchange.ts#L261-L279会兼容fetchOptions为函数或对象两种形态先解析出当前fetchOptions再通过makeOperation生成新 Operation把新 headers 与已有 headers 合并。测试用例 authExchange.test.ts#L56-L86 验证了Authorization头能正确写入operation.context.fetchOptions.headersL88-L121 则验证了异步初始化延迟拿到 token后头部依然能正确附加。如果你需要以其他方式更新上下文例如把 token 塞进自定义上下文字段可以改用makeOperationimport { makeOperation } from urql/core; makeOperation(operation.kind, operation, { ...operation.context, someAuthThing: token, });注意addAuthToOperation是同步函数返回新的Operation它会在每次操作转发前被调用源码中由addAuthToOperation包装函数触发见 authExchange.ts#L332-L334。配置didAuthError识别 API 的认证错误该函数告诉authExchange什么样的错误算作认证错误。authExchange在收到携带error的OperationResult时调用它这里的 error 类型是CombinedErrorurql 将 GraphQL 错误与网络错误统一包装的类型。我们可以检查CombinedError的graphQLErrors数组来判断是否发生认证错误。如果 API 在错误的extensions.code上携带错误码一个典型的认证错误结果长这样{ data: null, errors: [ { message: Unauthorized: Token has expired, extensions: { code: FORBIDDEN }, } ] }如果你正在设计新的 API在错误的extensions上附加元数据是推荐做法。随后即可判断任一 GraphQL 错误是否携带未授权错误码authExchange(async utils { // ... return { // ... didAuthError(error, _operation) { return error.graphQLErrors.some(e e.extensions?.code FORBIDDEN); }, }; });部分 GraphQL API 只通过 HTTP 401 状态码传达认证失败类似 RESTful API 的做法虽然不够理想我们也可以为其编写检查authExchange(async utils { // ... return { // ... didAuthError(error, _operation) { return error.response?.status 401; }, }; });当didAuthError返回true时会触发authExchange调用refreshAuth进行重新认证。仓库示例 examples/with-refresh-auth/src/client.js 中使用了e.extensions?.code UNAUTHORIZED来判断认证错误你可以按自己的 API 约定调整错误码。配置refreshAuth刷新令牌或登出如果 API 不支持任何令牌刷新机制refreshAuth里直接登出即可authExchange(async utils { // ... return { // ... async refreshAuth() { logout(); }, }; });这里的logout()是占位函数在收到错误时被调用用于清理令牌并重定向到登录页。如果支持用刷新令牌换新令牌可以首先尝试为用户获取新令牌authExchange(async utils { let token localStorage.getItem(token); let refreshToken localStorage.getItem(refreshToken); return { // ... async refreshAuth() { const result await utils.mutate(REFRESH, { refreshToken }); if (result.data?.refreshLogin) { // Update our local variables and write to our storage token result.data.refreshLogin.token; refreshToken result.data.refreshLogin.refreshToken; localStorage.setItem(token, token); localStorage.setItem(refreshToken, refreshToken); } else { // This is where auth has gone wrong and we need to clean up and redirect to a login page localStorage.clear(); logout(); } }, }; });这里使用了authExchange提供的特殊工具方法mutate。如果你的 GraphQL API 期望通过 GraphQL mutation 来更新认证状态mutate非常有用它会绕过所有前置 exchange 与authExchange自身的认证逻辑直接发送 mutation。从源码看mutate通过client.createRequestOperation(mutation, ...)构造操作并将其加入bypassQueue实现旁路authExchange.ts#L232-L260。测试用例 authExchange.test.ts#L123-L168 验证了在refreshAuth中调用utils.mutate发起的 mutation 会绕过认证头之外的流程、且带上了最新令牌。如果你的认证走的是 REST 端点而非 GraphQL这里也可以直接用fetchAPI 代替 mutation。重要行为当refreshAuth运行时所有其他请求都会被暂停因此你不需要担心并发触发多个认证错误或多次刷新。这一行为由源码中的重试队列与authPromise单例保护实现authExchange.ts#L302-L313只有当前没有正在进行的刷新时才会启动新的refreshAuth期间到达的操作统一进入retryQueue刷新完成后由flushQueue一并放行。测试 authExchange.test.ts#L389-L444 还验证了重试后的第二次认证错误不会引发无限重试这是通过 Operation 上下文中的authAttempt标记控制的。若refreshAuth自身抛错错误会被包装为CombinedError传给排队中的操作authExchange.test.ts#L446-L478。配置willAuthError在请求前预判认证失败willAuthError是可选参数在请求发出前被调用。我们可以用它预判认证失败从而让authExchange直接触发refreshAuth无需先让一个请求失败authExchange(async utils { // ... return { // ... willAuthError(_operation) { // Check whether token JWT is expired return false; }, }; });当明确知道认证状态已失效时这非常有用可以避免发送任何注定会以认证错误告终的操作。不过定义该函数时必须小心如果某些查询或登录 mutation 在未登录状态下也会正常发送到 API那么在这些场景下应当放行。更好的做法是识别允许放行的 mutation或在存储中还没有 token 时返回false。例如要识别一个永远不会触发认证错误的 mutation可以这样写authExchange(async utils { // ... return { // ... willAuthError(operation) { if ( operation.kind mutation // Here we find any mutation definition with the login field operation.query.definitions.some(definition { return ( definition.kind OperationDefinition definition.selectionSet.selections.some(node { // The field name is just an example, since signup may also be an exception return node.kind Field node.name.value login; }) ); }) ) { return false; } else if (false /* is JWT expired? */) { return true; } else { return false; } }, }; });示例代码 examples/with-refresh-auth/src/client.js 展示了更贴近实战的写法每次操作前先同步从存储中重新读取 token若没有 token则仅在操作不是signinmutation 时才返回true即让未登录的查询触发刷新/登出而放行登录操作。从源码看willAuthError的触发逻辑是!operation.context.authAttempt config.willAuthError config.willAuthError(operation)authExchange.ts#L315-L322即已重试过authAttempt为 true的操作不会再触发预判刷新。测试 authExchange.test.ts#L281-L324 验证了willAuthError返回true时请求不会立即发出而是在refreshAuth完成后以新令牌发出。通过mapExchange处理登出除了在authExchange内处理认证错误你也可以改用mapExchange响应错误并执行登出。做法是把mapExchange加入 exchanges 数组并放在authExchange之前——顺序非常关键import { createClient, cacheExchange, fetchExchange, mapExchange } from urql; import { authExchange } from urql/exchange-auth; const client createClient({ url: http://localhost:3000/graphql, exchanges: [ cacheExchange, mapExchange({ onError(error, _operation) { const isAuthError error.graphQLErrors.some(e e.extensions?.code FORBIDDEN); if (isAuthError) { logout(); } }, }), authExchange(async utils { return { /* config */ }; }), fetchExchange, ], });mapExchange只有在authExchange已经尝试处理过且处理失败后才会收到认证错误。这意味着要么刷新令牌失败要么 API 根本不支持刷新。只要在mapExchange的onError中收到认证错误判定标准与上文didAuthError一致就可以确信这是authExchange无法恢复的错误应当执行登出。urql/exchange-auth的 README 也特别强调mapExchange必须放在authExchange上方否则认证错误会在authExchange有机会处理之前就暴露出来。登出时的缓存失效重建 Client如果同一时刻需要处理多个认证状态例如登出场景必须确保认证状态变化时重新初始化Client否则旧的缓存与认证状态会继续残留。以下是在 React 中实现该模式的示例import { createClient, Provider } from urql; const App ({ isLoggedIn }: { isLoggedIn: boolean | null }) { const client useMemo(() { if (isLoggedIn null) { return null; } return createClient({ /* config */ }); }, [isLoggedIn]); if (!client) { return null; } return { Provider value{client} {/* app content */} Provider } }应用启动时第一件事是检查持久化存储中是否存在认证令牌以决定展示登录态还是登出态视图。isLoggedIn属性应始终随认证状态变化而更新用户完成认证且令牌写入存储后置为true用户登出且令牌清除后置为false。务必在更新该属性之前先完成存储的写入或清除这样authExchange才能基于正确的认证状态工作。该模式之所以特别有用是因为重新创建Client会同时重建客户端缓存从而使所有缓存数据失效。示例项目 examples/with-refresh-auth/src/App.jsx 便采用了这一思路Home组件依据getToken()初始化登录态登录成功后先saveAuthData(auth)再setIsLoggedIn(true)。小结完整的配置骨架综合以上四个回调一个完整的authExchange配置骨架如下可直接复制改造import { Client, cacheExchange, fetchExchange } from urql; import { authExchange } from urql/exchange-auth; const auth authExchange(async utils { let token localStorage.getItem(token); let refreshToken localStorage.getItem(refreshToken); return { addAuthToOperation(operation) { if (!token) return operation; return utils.appendHeaders(operation, { Authorization: Bearer ${token}, }); }, willAuthError(operation) { // 可选预判 JWT 是否过期注意放行登录类 mutation return false; }, didAuthError(error) { // 按你的 API 约定判断认证错误错误码或 HTTP 401 return error.graphQLErrors.some(e e.extensions?.code FORBIDDEN); }, async refreshAuth() { if (refreshToken) { const result await utils.mutate(REFRESH_MUTATION, { refreshToken }); if (result.data?.refreshLogin) { token result.data.refreshLogin.token; refreshToken result.data.refreshLogin.refreshToken; localStorage.setItem(token, token); localStorage.setItem(refreshToken, refreshToken); return; } } localStorage.clear(); logout(); }, }; }); const client new Client({ url: http://localhost:3000/graphql, exchanges: [cacheExchange, auth, fetchExchange], });在 urql 的 exchange 架构中authExchange是处理认证控制流的专用 exchange初始化时读取认证状态、请求前附加令牌、请求后识别认证错误、失败时串行刷新并重试一次、无法恢复时把错误交给上游的mapExchange处理。其完整控制流实现见 exchanges/auth/src/authExchange.ts行为验证见 exchanges/auth/src/authExchange.test.tsAPI 速览可参考 docs/api/auth-exchange.md 与 exchanges/auth/README.md端到端示例位于 examples/with-refresh-auth。按本文顺序依次配置addAuthToOperation、didAuthError、refreshAuth、willAuthError即可为你的 urql 应用建立完整、可恢复的 JWT 认证闭环。赞分享前端【免费下载链接】urqlThe highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.项目地址https://gitcode.com/gh_mirrors/ur/urql点击查看免费下载相关推荐urql 刷新认证Refresh Auth实战用 authExchange 构建登录、令牌刷新与登出全流程urql 刷新认证Refresh Auth实战用 authExchange 构建登录、令牌刷新与登出全流程 本文基于 urql 仓库中的 with ref前端urql authExchange 实战指南在 urql 中实现 JWT 认证与令牌刷新urql authExchange 实战指南在 urql 中实现 JWT 认证与令牌刷新 本指南以 docs/api/auth exchange.md htt前端CANN低比特量化算法开发任务7月社区任务 低比特量化算法开发任务书 基础信息 技术标签 量化算法开发 适配硬件 Atlas A5 训练系列产品/推理系列产品 开源仓地址 httpsCANN文档高性能计算上一篇EsreverUnicode字符串反转的终极解决方案下一篇探索React页面渲染的新境界React-Page项目深度解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

USB转I2C适配器如何跑通1000KHz总线速率:从扫描到调试的完整实践 2026/9/25 7:36:42

USB转I2C适配器如何跑通1000KHz总线速率:从扫描到调试的完整实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
智能仓储系统落地核心:库存模型、状态机与并发扣减实践 2026/9/25 7:36:42

智能仓储系统落地核心:库存模型、状态机与并发扣减实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
ESP32上跑WebAssembly:为何不能直接操作硬件?Host API桥接才是正解 2026/9/25 7:36:42

ESP32上跑WebAssembly:为何不能直接操作硬件?Host API桥接才是正解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
电流检测电路设计:运算放大器、PCB布局与采样电阻协同优化 2026/9/25 7:36:42

电流检测电路设计:运算放大器、PCB布局与采样电阻协同优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
DedeCMS蜘蛛爬行插件实战:主动推送与站内入口优化指南 2026/9/25 7:36:36

DedeCMS蜘蛛爬行插件实战:主动推送与站内入口优化指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
区块链货币与现代货币对比:从记账体系到信用锚的分析框架 2026/9/25 7:36:36

区块链货币与现代货币对比:从记账体系到信用锚的分析框架

简介:一份着眼于区块链货币与现代货币对比研究的PDF文献资料,适合关注加密数字货币、金融科技及货币制度演变的读者,也可作为相关课题的参考文献。文章来自2018年学术期刊,围绕区块链分布式不可更改加密数据库技术、现代货币体系中…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉