首页
/ freeCodeCamp 课程实战:在 Node.js + Express 项目里用 passport.socketio 为 Socket.IO 实现身份认证

freeCodeCamp 课程实战:在 Node.js + Express 项目里用 passport.socketio 为 Socket.IO 实现身份认证

2026-09-04 20:47:48作者:翟江哲Frasier

这篇指南基于 freeCodeCamp 课程仓库中 "Advanced Node and Express" 模块的 "Authentication with Socket.IO" 编程挑战(挑战文档),讲解为什么 WebSocket 连接上拿不到 req.user,以及如何用 passport.socketio + connect-mongo + cookie-parser 三件套,让 Socket.IO 复用 Express 会话中的 Passport 身份,最终在 socket.request.user 上直接读取到已登录用户对象。读完后,你将掌握在已有 Express/Passport 认证体系上为 Socket.IO 连接做授权的标准流程,理解 key(会话 cookie 名)、secretstore 三个核心配置项的由来,并知道测试脚本是如何验证这套配置的。

背景问题:WebSocket 连接上没有 req.user

在标准 Express 请求/响应模型中,Passport 认证完成后,req.user 里就存着当前用户对象,这是前面若干挑战(如 Passport 序列化用户对象)建立起来的基础。但一旦引入 Socket.IO 长连接,情况就不一样了:

  • 浏览器与服务器之间的 WebSocket 通道不经过 Express 的 HTTP 请求管线,因此没有 req(request)对象,自然也就没有 req.user
  • 此时服务器无法判断"这个 socket 是谁的",对于聊天室这类需要区分用户的场景是致命的。

挑战文档给出的思路是:浏览器在发起 Socket.IO 握手请求时,仍会携带 HTTP 请求头里的 Cookie,其中就包含 express-session 写入的会话 Cookie。只要把这个 Cookie 解析出来、用会话密钥解码出会话 id,再到 session store 中反序列化出用户对象,问题就解决了。passport.socketio 这个 npm 包正是把这条"复杂链路"封装成了一步配置。

依赖准备与 Require 方式

挑战说明中,以下三个包已经被添加为项目依赖(学员无需手动 npm install):

包名 版本范围 require 后的变量名 职责
passport.socketio ~3.7.0 passportSocketIo 提供 Socket.IO 认证中间件 authorize()
connect-mongo ~3.2.0 MongoStore 将 express-session 的会话数据持久化到 MongoDB
cookie-parser ~1.4.5 cookieParser 解析请求 Cookie,供 passport.socketio 读取会话 id

三个包分别 requirepassportSocketIoMongoStorecookieParser 即可,其中后两者是工厂式的引入方式。

创建 MongoDB 会话存储 MongoStore

passport.socketio 需要一个能根据会话 id 找到会话数据的存储,这里使用 connect-mongo 提供的 MongoStore。挑战文档要求"初始化一个新的 memory store(即 session store),来自之前已经 require 的 express-session",代码如下:

const MongoStore = require('connect-mongo')(session);
const URI = process.env.MONGO_URI;
const store = new MongoStore({ url: URI });

几个要点:

  • require('connect-mongo')(session)connect-mongo@~3.2.0 的典型用法:先调用工厂函数并传入 express-session 模块,返回 MongoStore 构造函数;
  • 连接串来自环境变量 MONGO_URI,与项目中 mongoose 使用的数据库保持一致,因此会话数据与用户数据落在同一个 MongoDB 实例上;
  • 这个 store 稍后会被同时交给 session 中间件和 passport.socketio 使用——这正是它能"认得"会话的关键。

用 io.use 注册 passport.socketio 认证中间件

在 Socket.IO 侧,需要用 io.use() 注册认证中间件。注意挑战文档强调:这段代码必须加在已有的 socket 代码之前,而不是放在现有的 connection 监听器内部。 完整配置如下:

io.use(
  passportSocketIo.authorize({
    cookieParser: cookieParser,
    key: 'express.sid',
    secret: process.env.SESSION_SECRET,
    store: store,
    success: onAuthorizeSuccess,
    fail: onAuthorizeFail
  })
);

逐项解析 authorize() 的选项:

选项 取值 说明
cookieParser cookieParser passport.socketio 用它从握手请求中解析出 Cookie 字符串
key 'express.sid' 会话 Cookie 的名称,必须与 session 中间件使用的 key 完全一致
secret process.env.SESSION_SECRET 签名/校验会话所需的密钥,与 session 中间件配置的 secret 同源
store 上面的 store 会话存储实例,用于根据会话 id 反序列化出 user
success / fail onAuthorizeSuccess / onAuthorizeFail 认证通过/失败时的回调函数

挑战文档特别指出:这套配置与之前给 API 配置 session 中间件的方式非常相似——因为它们本质上使用同一种认证机制:从 Cookie 中取会话 id,再到 store 中验证。

关键坑:key 必须在两处显式声明

这是本挑战最容易踩的坑,文档用了整整两段来强调:

  1. 之前配置 session 中间件时,没有显式设置会话 Cookie 的名称(key),因为 express-session 使用了默认值;
  2. 现在又加入了一个需要从同一枚 Cookie 读取相同值的包(passport.socketio),两边必须显式声明同一个 key

因此需要做两件事:

  • session 中间件补上 key: 'express.sid',与上面 Socket.IO 配置中的 key 保持一致;
  • session 中间件的 options 中、靠近设置 saveUninitialized: true 的位置,补上 store: store

这样 Socket.IO 认证时才知道"该去哪个 store 里、按哪枚 Cookie 关联到哪个会话"。express.sid 正是 express-session 默认的会话 Cookie 名,显式写出来只是让两个包"说同一种语言"。

定义 success 与 fail 回调

中间件认证完成后会调用你提供的回调,挑战文档要求的实现如下:

function onAuthorizeSuccess(data, accept) {
  console.log('successful connection to socket.io');

  accept(null, true);
}

function onAuthorizeFail(data, message, error, accept) {
  if (error) throw new Error(message);
  console.log('failed connection to socket.io:', message);
  accept(null, false);
}

回调约定的参数与行为:

  • onAuthorizeSuccess(data, accept)data 是解析出的用户数据;必须调用 accept(null, true) 放行该连接。这里的 accept(null, true) 第一个参数为 null 表示无错误,第二个布尔值决定连接是否被接受;
  • onAuthorizeFail(data, message, error, accept):如果 error 存在,直接抛出携带 message 的异常(例如会话密钥不匹配、store 中查不到会话等"硬错误");否则记录 message 并调用 accept(null, false) 拒绝这条 socket 连接——未认证用户将无法与服务器建立 Socket.IO 通道。

验证成果:socket.request.user 可用

配置完成后,用户对象会挂在 socket 对象上:

console.log('user ' + socket.request.user.username + ' connected');

把这一行放进 io.on('connection', ...) 监听器,服务器控制台就会打印出"谁"连接进来了。从源码结构看,这个能力正是后续挑战直接依赖的——紧随其后的 Announce New Users 挑战 就是基于 socket.request.user.username 向全体在线用户广播"某用户加入/离开了聊天室":

io.emit('user', {
  username: socket.request.user.username,
  currentUsers,
  connected: true
});

而本挑战又建立在 Set up the Environment 之上:那个挑战先用 http.createServer(app) + require('socket.io')(http) 把 Socket.IO 挂到 Express 应用上,并注册了第一个 io.on('connection', ...) 监听器——本挑战的 io.use(...) 就加在它之前。这三个挑战在 advanced-node-and-express 模块的 challengeOrder 中依次排为 589fc830f9fc0f352b528e74(Set up the Environment)、589fc831f9fc0f352b528e77(Authentication with Socket.IO)、589fc832f9fc0f352b528e78(Announce New Users),构成"搭建通道 → 通道加身份 → 用身份做广播"的完整链路。

测试脚本如何验证你的实现

该挑战的 hints(同样写在挑战文档末尾)是服务端断言,从四个角度检查学员提交的项目,值得照此自查:

  1. package.json 依赖检查:断言 dependencies 中同时存在 passport.socketiocookie-parser(分别通过 /_api/package.json 获取);
  2. require 检查:断言 server.js 中匹配 require('passport.socketio')require("passport.socketio")(正则 require\((['"])passport\.socketio\1\),引号成对);
  3. 中间件注册检查:断言 server.js 中匹配 io.use(\s*\w+\.authorize(,即必须以 Socket.IO 中间件形式调用 authorize()
  4. (文档描述层面还要求)keysecretstore 选项齐全,且 session 中间件与 Socket.IO 的 key 一致。

小结

本挑战的完整落地清单可以概括为六步:

  1. require 三个包:passportSocketIoMongoStorecookieParser
  2. new MongoStore({ url: process.env.MONGO_URI }) 创建会话存储 store
  3. session 中间件中显式补上 key: 'express.sid'store: store
  4. 在 socket 代码之前注册 io.use(passportSocketIo.authorize({...}))key/secret/store 与 session 侧对齐;
  5. 实现 onAuthorizeSuccess / onAuthorizeFail,分别 accept(null, true)accept(null, false)
  6. connection 监听器里通过 socket.request.user 读取用户名,验证认证生效。

这套方案的通用价值在于:它没有另造一套 WebSocket 认证(如 JWT 放 Authorization 头),而是完全复用了浏览器端已有的 express-session + Passport 体系,改动面最小、与 HTTP 侧行为天然一致——这也是它被选入 freeCodeCamp 课程的原因。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
982
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384