如何基于Cookie实现Apollo Client与Server的用户身份认证
方案反馈与实现建议
思路反馈
你当前的实现思路是完全合理的,属于httpOnly Cookie + JWT 认证模式下的标准实践:
- 利用httpOnly禁止前端读取Cookie,从根源上避免XSS攻击窃取身份凭证
- 应用初始化时主动请求校验身份,是无状态JWT模式下持久化登录状态的通用方案,逻辑简单且安全性可控。
具体实现步骤
1. 服务端先补充Cookie解析依赖
你当前的Express代码没有加Cookie解析中间件,首先需要安装并引入:
npm install cookie-parser
然后在服务端代码中注册中间件:
const cookieParser = require('cookie-parser'); // 在cors中间件之后注册 app.use(cookieParser());
2. 在Apollo Context中统一完成身份校验
把JWT校验逻辑放到Context中,所有请求会提前完成身份解析,后续Resolver可以直接取用校验结果:
const jwt = require('jsonwebtoken'); const server = new ApolloServer({ resolvers, typeDefs, context: async ({req, res}) => { // 初始化用户信息为空 let currentUser = null; try { // 读取Cookie中的token const token = req.cookies.id; if (token) { // 校验JWT有效性,密钥和你签发时用的一致 const decoded = jwt.verify(token, process.env.JWT_SECRET || 'supersecret'); // 可以在这里再查一次数据库拿到最新的用户信息,避免JWT里的信息过时 currentUser = await prisma.user.findUnique({ where: { id: decoded.id }, // 过滤掉敏感字段比如密码 select: { id: true, email: true, nickname: true } }); } } catch (err) { // JWT过期、篡改等异常直接忽略,保持currentUser为空即可 console.log('Token校验失败:', err.message); } return { req, res, prisma, currentUser // 把校验后的用户信息注入上下文 }; }, });
3. 新增当前用户查询接口,供前端初始化调用
首先在typeDefs里定义查询:
type User { id: ID! email: String! nickname: String } type Query { getCurrentUser: User }
然后编写对应的Resolver:
Query: { getCurrentUser: (_, __, { currentUser }) => { // 直接返回上下文中已经校验好的用户信息,未登录就是null return currentUser; } }
4. 前端实现登录状态持久化
在React应用根组件(比如App.jsx)初始化时调用查询接口,把用户信息存入全局状态或者Apollo缓存:
import { useQuery, gql } from '@apollo/client'; const GET_CURRENT_USER = gql` query GetCurrentUser { getCurrentUser { id email nickname } } `; function App() { const { data, loading } = useQuery(GET_CURRENT_USER); if (loading) return <div>加载中...</div>; // 把用户信息存入全局状态(比如useContext、Redux、Zustand都可以) const isLogin = !!data?.getCurrentUser; return ( <div className="App"> {/* 你的应用逻辑,根据isLogin判断是否展示登录态内容 */} </div> ); }
5. 需鉴权的Resolver直接复用上下文信息
后续需要登录才能调用的接口,直接判断上下文中的currentUser是否存在即可:
// 举个例子:获取当前用户的订单列表 Query: { getMyOrders: async (_, __, { currentUser, prisma }) => { if (!currentUser) throw new Error('请先登录'); return prisma.order.findMany({ where: { userId: currentUser.id } }); } }
优化建议
- 签发JWT时不要传入完整的user对象,尤其是不要包含密码字段,仅传入用户ID等必要信息即可,避免敏感信息泄露
- JWT签名密钥不要硬编码在代码中,放到环境变量里管理,生产环境要使用复杂度足够的密钥
- 新增退出登录接口,直接清除Cookie即可:
Mutation: { logout: (_, __, { res }) => { res.clearCookie('id', { httpOnly: true, secure: process.env.NODE_ENV === 'production' }); return true; } }
- 生产环境建议增加CSRF校验,避免跨站请求伪造风险
内容的提问来源于stack exchange,提问作者Peter Boomsma
相关产品推荐
相关产品推荐

