使用Jest与Supertest测试HttpOnly刷新令牌端点失败,是否可行?
同事认为HttpOnly Cookie无法被浏览器交互,没法做集成测试;但Discord有人说HttpOnly仅限制浏览器,Jest不受此约束。我做了测试验证:目标是将登录接口返回的HttpOnly刷新令牌传入刷新接口获取新令牌,预期接口能打印令牌,实际结果却是undefined。
技术栈:Jest、Supertest、Express服务器
集成测试代码
import request from "supertest"; const api = request(server); // 测试用例内代码 const loginResponse = await api.post("/auth/email/login").send({ email: signupPayload.email }); // 登录请求已成功 const rt1 = loginResponse.headers["set-cookie"][0].split("=")[1].split(";")[0]; const x = loginResponse.headers["set-cookie"][0]; // 此处可正常打印刷新令牌Cookie及其值 const refreshResponse = await api.post("/auth/jwt/refresh").set("refreshToken", x).send({});
无论用rt1还是x调用.set("refreshToken", arg)都无效,控制器中令牌值始终为undefined。
控制器代码
// 设置刷新令牌的控制器方法 public async emailLogin(req: Request, res: Response) { // 省略部分逻辑 const cookieOptions = { httpOnly: true, expires: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), }; res.cookie("refreshToken", newRefreshToken, cookieOptions); return res.status(200).json({ member, accessToken }); } // 刷新令牌的控制器方法 public async refreshJwt(req: Request, res: Response) { try { const token = req.cookies.refreshToken; console.log(token, "refresh token"); // 此处值为undefined! // 省略剩余逻辑 } catch (err) { // 错误处理 } }
补充信息
我能正常打印出刷新令牌Cookie的值:refreshToken=a511d8a55839ee6a7d6102b3c0a184a432de160a3bd0e8a2c679e5fed272f3cac87eb17cfbff5af3; Path=/; Expires=Thu, 29 Dec 2022 23:33:24 GMT; HttpOnly,看起来内容正常但请求依然失败。
请问我是不是被误导了?用Jest和Supertest测试带HttpOnly刷新令牌Cookie的后端到底可行吗?还是我的操作哪里错了?
你的问题出在Cookie的发送方式不符合HTTP规范,和HttpOnly属性无关:
- Cookie不是通过自定义的
refreshToken请求头传递,而是必须放在Cookie请求头中。你直接用.set("refreshToken", x)是把完整Cookie字符串放到了一个自定义头里,后端的req.cookies是解析Cookie头的内容,自然拿不到值。 - HttpOnly只是限制浏览器前端JS通过
document.cookie访问Cookie,对HTTP请求本身没有限制——不管是浏览器还是Supertest,只要按规范发送Cookie头,后端就能正常接收,所以Discord的说法是对的,Jest完全可以测试这类接口。
正确的测试写法
直接把登录接口返回的set-cookie内容放到Cookie请求头中即可:
// 登录请求成功后 const cookies = loginResponse.headers["set-cookie"]; // 发送刷新请求时设置Cookie头 const refreshResponse = await api.post("/auth/jwt/refresh") .set("Cookie", cookies) // Supertest会自动处理Cookie数组格式 .send({});
如果只需要传递refreshToken这一个Cookie,也可以单独筛选:
const refreshTokenCookie = loginResponse.headers["set-cookie"].find(cookie => cookie.startsWith("refreshToken=")); const refreshResponse = await api.post("/auth/jwt/refresh") .set("Cookie", refreshTokenCookie) .send({});
额外提示
从你的代码能看出Express应用使用了cookie-parser中间件,它会自动解析Cookie头的内容并挂载到req.cookies上,只要正确设置Cookie头,就能正常拿到req.cookies.refreshToken的值。
内容的提问来源于stack exchange,提问作者plutownium

