【免费下载链接】howtographql
The Fullstack Tutorial for GraphQL
本篇指南基于 How To GraphQL 教程中 React + Apollo 前端的 Authentication 章节,带你完整走通一套前后端配合的认证方案:用signup/login两个 GraphQL Mutation 获取 JWT、把 token 存入浏览器、再通过 Apollo 的setContextLink 为每个请求自动附加Authorization头,最终让服务端识别“以谁的身份”发起请求并据此关联数据。
完整的认证链路可以概括为四步:
- 用户在
/login页面提交表单,前端通过useMutation发起signup或loginMutation; - Mutation 返回
token,前端将其写入localStorage并跳转回首页; - 之后
ApolloClient发出的每个请求,都会经过authLink这个中间件把 token 拼进Authorization: Bearer <token>请求头; - 服务端从
Authorization头中解析出userId放入 resolver 的context,从而在创建Link时自动关联postedBy。
以下代码示例均来自教程配套的hackernews-react-apollo前端项目与server后端项目,章节原文见 5-authentication.md。
一、准备 Login 组件与路由
1. Login 组件:两种状态的登录/注册表单
认证功能的起点是src/components/Login.js组件。它用一份formState同时承载登录和注册两种模式:
formState.login为true时,面向已有账户的用户,只渲染email和password两个输入框;formState.login为false时,面向尚未注册的用户,额外渲染一个name输入框用于创建账户。
提交按钮的onClick稍后会绑定到两个 Mutation,此处先保留占位逻辑:
// src/components/Login.js import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; const Login = () => { const navigate = useNavigate(); const [formState, setFormState] = useState({ login: true, email: '', password: '', name: '' }); return ( <div> <h4 className="mv3"> {formState.login ? 'Login' : 'Sign Up'} </h4> <div className="flex flex-column"> {!formState.login && ( <input value={formState.name} onChange={(e) => setFormState({ ...formState, name: e.target.value }) } type="text" placeholder="Your name" /> )} <input value={formState.email} onChange={(e) => setFormState({ ...formState, email: e.target.value }) } type="text" placeholder="Your email address" /> <input value={formState.password} onChange={(e) => setFormState({ ...formState, password: e.target.value }) } type="password" placeholder="Choose a safe password" /> </div> <div className="flex mt3"> <button className="pointer mr2 button" onClick={() => console.log('onClick')} > {formState.login ? 'login' : 'create account'} </button> <button className="pointer button" onClick={(e) => setFormState({ ...formState, login: !formState.login }) } > {formState.login ? 'need to create an account?' : 'already have an account?'} </button> </div> </div> ); }; export default Login;第二个按钮通过login: !formState.login在两种模式间切换,从而在同一个页面里复用了登录与注册两个流程。
2. 定义 localStorage 中 token 的键名
在src下新建constants.js,统一声明用于在浏览器localStorage中存放凭据的键名:
// src/constants.js export const AUTH_TOKEN = 'auth-token';安全提示:把 JWT 存放在
localStorage并不是前端认证的安全做法(一旦页面存在 XSS,token 即可被脚本读取并随请求发走)。本教程之所以这样处理,是为了聚焦 GraphQL 本身、保持示例简洁。生产环境应结合 HttpOnly Cookie、更短的 token 有效期与刷新机制等方案来权衡,切勿直接照搬。
3. 注册 /login 路由
打开App.js,把Login加入路由表:
// src/components/App.js // ... import Login from './Login'; const App = () => { return ( <div className="center w85"> <Header /> <div className="ph3 pv1 background-gray"> <Routes> <Route path="/" element={<LinkList/>} /> <Route path="/create" element={<CreateLink/>} /> <Route path="/login" element={<Login/>} /> </Routes> </div> </div> ); }; export default App;4. Header 组件:按登录态渲染入口
打开Header.js,让它根据localStorage中是否存在 token 来切换界面:未登录时右侧显示login入口、不渲染submit入口;已登录时显示logout,并放出submit入口(提交新链接)。这样在 UI 层就能保证“只有认证用户才看得到创建链接的入口”:
// src/components/Header.js import React from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { AUTH_TOKEN } from '../constants'; const Header = () => { const navigate = useNavigate(); const authToken = localStorage.getItem(AUTH_TOKEN); return ( <div className="flex pa1 justify-between nowrap orange"> <div className="flex flex-fixed black"> <Link to="/" className="no-underline black"> <div className="fw7 mr1">Hacker News</div> </Link> <Link to="/" className="ml1 no-underline black"> new </Link> <div className="ml1">|</div> <Link to="/search" className="ml1 no-underline black" > search </Link> {authToken && ( <div className="flex"> <div className="ml1">|</div> <Link to="/create" className="ml1 no-underline black" > submit </Link> </div> )} </div> <div className="flex flex-fixed"> {authToken ? ( <div className="ml1 pointer black" onClick={() => { localStorage.removeItem(AUTH_TOKEN); navigate(`/`); }} > logout </div> ) : ( <Link to="/login" className="ml1 no-underline black" > login </Link> )} </div> </div> ); }; export default Header;两处细节值得注意:
authToken = localStorage.getItem(AUTH_TOKEN)是整个组件渲染逻辑的开关:没有 token 时submit不渲染;- 点击logout时执行
localStorage.removeItem(AUTH_TOKEN)并navigate('/')回到首页——因为 token 是前端唯一的状态源,清掉它即完成登出,下次请求自然不再携带Authorization头。
二、实现 signup 与 login 两个 Mutation
signup和login就是两个普通的 GraphQL Mutation,用法与前面章节里的createLink完全一致。在Login.js顶部定义两份 mutation 文档:
// src/components/Login.js 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 之后会被附加到所有后续请求上,用来向服务端表明“请求是以该用户的名义发出的”。
1. 用 useMutation 绑定表单
把提交按钮的onClick改为三目表达式,按当前状态分别调用login或signup:
<div className="flex mt3"> <button className="pointer mr2 button" onClick={formState.login ? login : signup} > {formState.login ? 'login' : 'create account'} </button> <button className="pointer button" onClick={(e) => setFormState({ ...formState, login: !formState.login }) } > {formState.login ? 'need to create an account?' : 'already have an account?'} </button> </div>然后在组件内用 Apollo 的useMutationhook 创建这两个 mutation 触发器:
const [login] = useMutation(LOGIN_MUTATION, { variables: { email: formState.email, password: formState.password }, onCompleted: ({ login }) => { localStorage.setItem(AUTH_TOKEN, login.token); navigate('/'); } }); const [signup] = useMutation(SIGNUP_MUTATION, { variables: { name: formState.name, email: formState.email, password: formState.password }, onCompleted: ({ signup }) => { localStorage.setItem(AUTH_TOKEN, signup.token); navigate('/'); } });三个要点:
variables直接取自表单状态,mutation 执行时把当前输入作为变量发送;onCompleted回调在 mutation 成功完成后触发:先把响应中的login.token/signup.token写入localStorage(键名即AUTH_TOKEN),再跳转回首页;- 别忘了补齐 import:
import { useMutation, gql } from '@apollo/client'; import { useNavigate } from 'react-router-dom'; import { AUTH_TOKEN } from '../constants';2. 本地验证
运行yarn start,打开http://localhost:3000/login,点击need to create an account?切换到注册模式,填写name、email、password后点击create account。一切正常的话,应用会跳回首页,并且 Header 中submit按钮重新出现——说明 token 已写入localStorage。
要确认用户确实落库,可以在 database 项目的 dev Playground 中发送users查询验证新用户存在。
三、用 Apollo Link 为所有请求附加认证头
登录成功只是拿到了 token,真正的关键是让之后发出的每一个 GraphQL 请求都自动携带它。由于所有 API 请求都是由应用根部的ApolloClient实例创建并发送的,需要在index.js中为它配置一个认证 Link。
Apollo 的 Link 机制允许你构建“中间件”:在请求到达httpLink之前对其进行修改。认证正是这一机制的典型用法——用setContext创建一个authLink,放在httpLink与ApolloClient实例化之间:
// src/index.js const authLink = setContext((_, { headers }) => { const token = localStorage.getItem(AUTH_TOKEN); return { headers: { ...headers, authorization: token ? `Bearer ${token}` : '' } }; });配套 import:
import { setContext } from '@apollo/client/link/context'; import { AUTH_TOKEN } from './constants';工作原理分两步:每次ApolloClient发请求时都会执行这个回调;回调先从localStorage取出 token(如果存在),然后把authorization: Bearer <token>合并进请求头返回给 context,供后续的httpLink读取并真正发出 HTTP 请求。
最后把 link 链交给客户端:
const client = new ApolloClient({ link: authLink.concat(httpLink), cache: new InMemoryCache() });authLink.concat(httpLink)表示请求先经过authLink注入请求头,再交给httpLink发起网络调用。至此,只要localStorage里有 token,所有 API 请求都会被自动认证;登出后(token 被移除),authorization头变为空串,请求即回到匿名状态。
这正是本章节配套测验考察的核心点:“ApolloClient 发出的 HTTP 请求是如何认证的?”正确答案不是给ApolloClient传参或给它传一个认证方法,而是通过专门的 Apollo Link 中间件把认证 token 附加到请求上。
四、服务端如何消费这个 token
前端的最终目的,是让服务端能识别请求身份。教程的后端项目(server)在server/src/resolvers/Mutation.js中的postresolver 展示了这一过程:
// server/src/resolvers/Mutation.js async function post(parent, args, context, info) { const { userId } = context; let postedBy = undefined if (userId) { postedBy = { connect: { id: userId } } } const newLink = await context.prisma.link.create({ data: { url: args.url, description: args.description, postedBy } }); context.pubsub.publish('NEW_LINK', newLink); // not important for now return newLink; }逐行拆解:
const { userId } = context:userId是服务端在构造 request context 时,从Authorization请求头中解析 JWT 得到的。请求未携带该头时userId为undefined;- 若
userId存在,则构造postedBy = { connect: { id: userId } },用 Prisma 的connect写法把即将创建的Link直接外键关联到提交它的User; - 若用户未登录,
postedBy保持undefined,Prisma 会忽略该字段,Link便没有归属用户。教程特意允许“无主链接”存在,仅出于演示考虑,生产应用通常应改为强制认证; context.pubsub.publish('NEW_LINK', newLink)会在后续订阅章节中发挥作用,用于向订阅者推送新链接事件。
对照服务端实现:token 从哪里来、userId 如何入 context
本仓库中配套的后端教程 graphql-js 认证章节 给出了同一套方案的完整服务端实现,可对照理解前后端是如何咬合的:
- token 的签发:
signupresolver 先用bcrypt.hash(args.password, 10)对密码做哈希入库,再执行jwt.sign({ userId: user.id }, APP_SECRET),把userId装进 JWT 载荷并用APP_SECRET签名(见 signup/login resolver);loginresolver 则按email查库、用bcrypt.compare比对密码,校验通过后签发同样的 token; - userId 的解析:工具函数
getUserId从req.headers.authorization取出Bearer前缀后的 token,调用jwt.verify(token, APP_SECRET)验证签名后解出userId,任何一步失败都会抛Not authenticated异常(见 getUserId 实现); - context 的组装:Apollo Server 的
context被写成一个函数,在每个请求到来时执行userId: req && req.headers.authorization ? getUserId(req) : null,从而把“认证后的身份”注入context,供post等 resolver 直接读取(见 context 配置)。
把前后端两段代码放在一起看,整条链路就非常清晰了:前端authLink写入的Authorization: Bearer <token>头,正是服务端getUserId读取并验证的同一份数据;userId进入context后,又被postresolver 用于connect关联postedBy。前端负责“把身份带过去”,服务端负责“验证身份并消费它”,中间没有任何额外通道。
小结
本章节完成的认证方案可以归纳为四个关键件:
| 组件 | 位置 | 职责 |
|---|---|---|
Login组件 + 路由 | src/components/Login.js、App.js | 提供登录/注册双态表单,触发对应 Mutation |
AUTH_TOKEN常量 | src/constants.js | 统一localStorage中的 token 键名,贯穿组件与 Link |
authLink(setContext) | src/index.js | 每次发请求前把localStorage中的 token 写入Authorization: Bearer头 |
postresolver | server/src/resolvers/Mutation.js | 从context.userId(由 JWT 解析而来)自动关联postedBy |
这套模式——“Mutation 返回 token、客户端中间件统一附加请求头、服务端从 context 读取身份”——是 GraphQL 项目中最常见的无状态认证方式。理解了它之后,后续章节的投票、订阅等功能都可以建立在这个身份体系之上继续扩展(教程整体章节规划可参考 React + Apollo 课程结构)。
【免费下载链接】howtographql
The Fullstack Tutorial for GraphQL
相关推荐
How To GraphQL React + Apollo 前端教程:用 Apollo Client 构建 Hackernews 克隆应用
How To GraphQL React + Apollo 前端教程:用 Apollo Client 构建 Hackernews 克隆应用 本篇是 How To
How to GraphQL React 教程:用 Apollo Client 的 useMutation 发送 Mutation 创建 Link
How to GraphQL React 教程:用 Apollo Client 的 useMutation 发送 Mutation 创建 Link 本文基于 H
CoreUI React用户登录与注册:完整认证流程实现指南
CoreUI React用户登录与注册:完整认证流程实现指南 想要快速构建现代化的React管理后台系统吗?CoreUI Free React Admin Te
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考