You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 12:00:02