【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载本文是 How to GraphQL 教程前端 urql 技术栈中“认证”章节的完整实战指南讲解如何在 React urql 应用中为 Hacker News 克隆实现 signup注册与 login登录功能并把登录后拿到的 token 自动附加到每一次 GraphQL 请求上同时保证服务端只有已认证用户才能创建新链接。读完本文你将掌握 urql 的useMutation认证流程、Client的fetchOptions配置方式以及前后端配合校验Authorization头的完整链路。认证的整体流程概览在 urql 应用中邮箱密码认证的完整链路可以拆成四个环节发起认证 mutation前端通过signup/login两个 GraphQL mutation 提交邮箱、密码注册时还有用户名。服务端签发 token服务端校验凭据后返回一个token字符串本教程中即 JWT随AuthPayload一起返回。相关后端 schema 定义可见前端入门章节中的应用 schematype Mutation { post(url: String!, description: String!): Link! signup(email: String!, password: String!, name: String!): AuthPayload login(email: String!, password: String!): AuthPayload } type AuthPayload { token: String user: User }前端存储 token把 token 写入浏览器localStorage之后用它判断用户是否已登录。携带 token 发请求配置 urqlClient让fetchExchange在发出每个请求时自动附加Authorization: Bearer token头服务端解析该头完成鉴权并把当前用户与新建的 Link 关联起来。下文将按这四个环节逐步实现。准备 token 逻辑封装 localStorage先写一个小工具模块把 localStorage 的读写细节封装起来方便后续在多个组件中复用。在src目录下新建token.jsconst AUTH_TOKEN auth-token; export const getToken () localStorage.getItem(AUTH_TOKEN); export const setToken token localStorage.setItem(AUTH_TOKEN, token); export const deleteToken () localStorage.removeItem(AUTH_TOKEN);三个函数各司其职getToken()返回当前存储的 token用户未登录时返回null。后续Header组件正是用它来判断登录状态。setToken(token)把 token 写入 localStorage登录/注册成功后调用。deleteToken()登出时移除 token。安全提示把 JWT 存放在localStorage并不是前端认证的安全做法易受 XSS 攻击。本教程聚焦 GraphQL 本身为了保持简单才这样做在生产项目中应优先考虑更安全的存储方案或使用 httpOnly cookie。准备 React 组件Login 页面与路由构建 Login 组件在src/components下新建Login.js先搭建只有 UI、没有逻辑的表单。它有两个状态isLogin为true时是登录表单只显示邮箱、密码两个输入框为false时是注册表单额外多一个用户名输入框import React from react import { setToken } from ../token const Login props { // Used to switch between login and signup const [isLogin, setIsLogin] React.useState(true) const [email, setEmail] React.useState() const [password, setPassword] React.useState() const [name, setName] React.useState() return ( div h4 classNamemv3{isLogin ? Login : Sign Up}/h4 div classNameflex flex-column {!isLogin ( input value{name} onChange{e setName(e.target.value)} typetext placeholderYour name / )} input value{email} onChange{e setEmail(e.target.value)} typetext placeholderYour email address / input value{password} onChange{e setPassword(e.target.value)} typepassword placeholderChoose a safe password / /div div classNameflex mt3 button typebutton classNamepointer mr2 button {isLogin ? login : create account} /button button typebutton classNamepointer button onClick{() setIsLogin(!isLogin)} {isLogin ? need to create an account? : already have an account?} /button /div /div ) } export default Login组件顶部已经导入了setToken后面会在 mutation 成功后用它更新 token第一个按钮的onClick处理器也将在后续步骤中挂上。添加 /login 路由打开App.js把Login组件注册到路由中。这里沿用了路由章节里配置好的react-routerv5 与LoadingBoundaryimport React from react import { Switch, Route } from react-router-dom import LoadingBoundary from ./LoadingBoundary import Header from ./Header import LinkList from ./LinkList import CreateLink from ./CreateLink import Login from ./Login const App () ( div classNamecenter w85 Header / div classNameph3 pv1 background-gray LoadingBoundary Switch Route exact path/ component{LinkList} / Route exact path/create component{CreateLink} / Route exact path/login component{Login} / /Switch /LoadingBoundary /div /div ) export default App在 Header 中接入登录状态与登出最后更新Header.js用getToken()判断登录状态登录后才显示 submit 链接确保只有已认证用户能创建链接右侧按钮在登录/登出之间切换点击登出时调用deleteToken()并跳回首页import React, { Component } from react import { withRouter } from react-router import { Link } from react-router-dom import { getToken, deleteToken } from ../token const Header props { const isLoggedIn !!getToken(); return ( div classNameflex pa1 justify-between nowrap orange div classNameflex flex-fixed black div classNamefw7 mr1Hacker News/div Link to/ classNameml1 no-underline black new /Link {isLoggedIn ( div classNameflex div classNameml1|/div Link to/create classNameml1 no-underline black submit /Link /div )} /div div classNameflex flex-fixed {isLoggedIn ? ( div classNameml1 pointer black onClick{() { deleteToken() props.history.push(/) }} logout /div ) : ( Link to/login classNameml1 no-underline black login /Link )} /div /div ) } export default withRouter(Header)!!getToken()把 token 字符串转成布尔值token 存在即为已登录。withRouter使Header能拿到props.history从而在登出后完成页面跳转。到这里登录相关的 UI 骨架就绪可以开始写认证 mutation 了。添加认证 mutationssignup 与 loginsignup和login就是两个普通的 GraphQL mutation用法与创建链接章节中的postmutation 完全一致。在Login.js顶部加入导入和 mutation 定义import gql from graphql-tag import { useMutation } from urql import { setToken } from ../token const SIGNUP_MUTATION gql mutation SignupMutation($email: String!, $password: String!, $name: String!) { signup(email: $email, password: $password, name: $name) { token } } const LOGIN_MUTATION gql mutation LoginMutation($email: String!, $password: String!) { login(email: $email, password: $password) { token } } 两个 mutation 结构高度相似接收一组参数返回一个可用于认证后续请求的token。注意它们请求的字段只有token因此 mutation 结果里也只会有token——这正是查询章节强调的“请求什么字段就返回什么数据”所以后面从结果data里读取时必须根据当前是登录还是注册分别从login或signup键取值。用一个 useMutation 同时处理登录与注册由于两个 mutation 的返回结构相同、且同一时刻只会执行其中一个可以只写一个useMutationhook 来覆盖两种场景。回顾一下useMutation的约定详见mutations 章节它只接收 mutation 定义这一个参数返回[state, executeMutation]数组executeMutation接收变量并返回一个 Promisestate则持续反映当前 mutation 的执行状态含fetching。在Login组件中加入 hook 和mutate处理器const Login props { // ... const [state, executeMutation] useMutation( isLogin ? LOGIN_MUTATION : SIGNUP_MUTATION ); const mutate React.useCallback(() { executeMutation({ email, password, name }) .then(({ data }) { const token data data[isLogin ? login : signup].token if (token) { setToken(token) props.history.push(/) } }); }, [executeMutation, props.history, isLogin, email, password, name]); // ... };逻辑链路很清晰用户选择登录时传入LOGIN_MUTATION选择注册时传入SIGNUP_MUTATIONmutate处理器调用executeMutation把表单里的email、password以及注册时的name作为变量传入mutation 完成后从返回结果data中按isLogin ? login : signup取出token若 token 存在调用setToken(token)写入 localStorage并用props.history.push(/)跳回首页。这里也体现了useMutation同时提供 Promise 与state的设计初衷Promise 用来在 mutation 完成后执行副作用如跳转页面state.fetching则用于 mutation 进行中改变 UI如禁用按钮。把处理器挂到按钮上最后更新按钮区域把onClick{mutate}挂到第一个按钮上并用state.fetching在请求进行中禁用两个按钮防止重复提交div classNameflex mt3 button typebutton classNamepointer mr2 button disabled{state.fetching} onClick{mutate} {isLogin ? login : create account} /button button typebutton classNamepointer button disabled{state.fetching} onClick{() setIsLogin(!isLogin)} {isLogin ? need to create an account? : already have an account?} /button /div至此认证 mutation 全部完成。可以运行yarn start并访问http://localhost:3000/login点击 need to create an account? 切换到注册模式填好用户名、邮箱、密码并提交成功后应用会跳回首页Header 中的 submit 链接也会重新出现。还可以到后端database 项目的 dev Playground 中发送users查询验证新用户确实已写入数据库。注意与查询一样mutation 返回的数据取决于你请求了哪些字段所以这里必须根据情况从结果data的login或signup键上读取 token。配置 urql Client让每个请求都带上 token用户拿到 token 只是第一步关键还在于让 token 随请求一起发送。由于所有 API 请求都由 urql 的Client创建和发出需要让Client知道用户当前的 token。在入门章节中Client配置了一组 exchangesdedupExchange、cacheExchange、fetchExchange。其中fetchExchange负责用fetch实际发送 GraphQL 请求而Client的fetchOptions选项可以提供一个函数该函数会在每一次请求发出前被fetchExchange调用其返回值会合并进默认的 fetch 参数。因此只需修改src/index.js让fetchOptions从 localStorage 读取 token 并写入Authorization头import { getToken } from ./token // ... const client new Client({ url: http://localhost:4000, fetchOptions: () { const token getToken() return { headers: { authorization: token ? Bearer ${token} : } } }, exchanges: [dedupExchange, cache, fetchExchange] })要点说明fetchOptions被定义为一个函数而非静态对象这样每次请求时都会重新读取最新的 token登录/登出后无需重建 Client 也能生效token 存在时发送authorization: Bearer token不存在时发送空字符串等价于不携带凭证dedupExchange在最前、fetchExchange在最后的顺序保持不变fetchExchange会把fetchOptions的返回值附加到它默认的fetch参数上详见入门章节中关于 exchanges 的说明。这样应用里所有 GraphQL 操作在 token 可用时都会自动带上Authorization头服务端据此识别当前登录用户并执行鉴权。进阶提示在完全生产化的应用里可能会遇到需要动态刷新 token、或无法同步读取 token 的场景此时更合适的方式是编写一个自定义 Exchange 来统一处理认证逻辑urql 官方提供了 auth-exchange 的实现思路供参考。服务端强制认证只有登录用户才能发帖前端只是发送Authorization头真正的安全边界在服务端。本教程所用的后端代码来自 Node 后端教程的最终项目其中认证机制的完整实现JWT 签发、bcrypt密码哈希、getUserId校验可参考后端认证章节。以postmutation 为例服务端解析器从请求的Authorization头中提取userId并将其作为创建 Link 的postedBy关联。在server/src/resolvers/Mutation.js中function post(parent, { url, description }, context) { const userId getUserId(context) return context.prisma.createLink({ url, description, postedBy: { connect: { id: userId } } }) }这段代码的核心行为getUserId(context)从请求的Authorization头中取出并校验 JWT返回当前用户的 ID。后端getUserId的典型实现是先读取req.headers.authorization去掉Bearer前缀后用APP_SECRET验证 JWT、取出userId如果 token 缺失或无效它会直接抛出异常从而在createLink执行前就中断本次操作GraphQL 响应只包含“未认证”错误参见后端认证章节中utils.js的getUserId实现。拿到userId后通过 Prisma 的嵌套写操作postedBy: { connect: { id: userId } }把新创建的 Link 与当前用户关联起来实现“每条 Link 的postedBy自动指向发帖人”的效果。这种“服务端解析 token → 抛异常拒绝未认证请求 → 关联当前用户”的模式正是前端认证与后端鉴权的衔接点前端负责携带凭证服务端负责验证凭证并据此授权。端到端验证与常见问题完成以上所有步骤后完整的认证闭环即可验证启动后端进入server目录运行yarn start服务运行在http://localhost:4000同时打开 GraphQL Playground启动前端在项目根目录运行yarn start访问http://localhost:3000/login注册一个用户提供name、email、password提交后应用跳回首页此时到 Playground需选择 app 项目的默认 Playground并配置Authorization: Bearer token的 HTTP Header发送postmutation即可创建一条关联到当前用户的 Link若不携带有效 tokenpost会因getUserId抛错而失败发送loginmutation 可再次获取 token并通过返回的user.links字段确认之前创建的链接归属参见后端认证章节的测试流程。常见问题排查前端报错且页面空白多半是后端没有运行。注意需要同时保持两个进程一个跑 React 应用yarn start一个跑 GraphQL 服务server目录下yarn start。请求没有携带 token检查src/index.js中fetchOptions是否以函数形式配置并确认登录后 localStorage 中存在auth-token键。postmutation 报未认证错误确认 Playground 的 HTTP HEADERS 已正确配置{Authorization: Bearer token}且 token 未过期、未被篡改。小结本章完成了 React urql 应用中的完整认证功能核心收获有三点token 生命周期管理通过token.js统一封装getToken/setToken/deleteToken配合Header组件实现基于登录状态的 UI 切换未登录隐藏 submit、提供 login/logout 入口认证 mutation 的声明式写法用同一个useMutationhook 按isLogin状态切换LOGIN_MUTATION/SIGNUP_MUTATION通过executeMutation返回的 Promise 完成“存 token → 跳首页”的副作用并用state.fetching控制按钮禁用请求级鉴权闭环Client的fetchOptions函数让fetchExchange在每次请求时自动附加Authorization: Bearer token服务端则通过getUserId校验 token、拒绝未认证请求并把当前用户与新建数据关联。这套“前端存储并携带凭证、服务端解析并强制鉴权”的模式是 GraphQL 应用中认证功能的标准骨架可平滑迁移到投票vote、订阅等其它需要登录态的功能上。赞分享【免费下载链接】howtographqlThe Fullstack Tutorial for GraphQL项目地址https://gitcode.com/gh_mirrors/ho/howtographql点击查看免费下载相关推荐How To GraphQL用 React Apollo Client 实现登录注册与请求级认证How To GraphQL用 React Apollo Client 实现登录注册与请求级认证 本篇指南基于 How To GraphQL 教程中 ReJava GraphQL 服务端认证实战邮箱密码注册、Token 登录与 Authorization 请求鉴权graphql-javaJava GraphQL 服务端认证实战邮箱密码注册、Token 登录与 Authorization 请求鉴权graphql java 本文基于 howtWasp 邮箱认证实战指南登录注册、邮箱验证、密码重置与自定义扩展Wasp 邮箱认证实战指南登录注册、邮箱验证、密码重置与自定义扩展 Wasp 为全栈 JS/TS 应用提供了开箱即用的邮箱认证Email AuthenticWeb框架后端前端CLI开发工具上一篇一句话生成专业报表JimuReport AI报表工具终极指南下一篇vim-plug插件推荐为Markdown写作者精选的编辑工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
