我正在尝试使用Express、JWT和Prisma创建一个基本的身份验证系统。我的前端是用Next/React写的。我想使用仅限HTTP的Cookie执行JWT验证。当用户最初从前端登录到他们的帐户时,令牌cookie会成功创建(我使用浏览器的开发工具确认了这一点)。每当我尝试使用req.cookies
从cookie-parser
验证JWT时,它总是返回一个空对象[Object: null prototype] {}
,即使在确认浏览器中已设置cookie之后也是如此。
我的express应用程序是这样配置的(注意:我在调试时将所有内容压缩到一个文件中):Front-end: http://localhost:3000
Back-end: http://localhost:5000
import { Express } from "express";
import { matchedData, validationResult, body } from "express-validator"
// import memberdata from "./api/memberdata.js"
// import auth from "./auth/auth.js"
import express from "express";
import cookieParser from "cookie-parser"
import cors from "cors";
import prisma from "./client.js"
import jwt from "jsonwebtoken"
import dotenv from "dotenv"
dotenv.config()
const app:Express = express();
const port = 5000;
app.use(cookieParser());
app.use(cors({
credentials: true,
origin: "http://localhost:3000"
}));
app.use(express.json());
// initial authentication route
app.post("/auth", body("email").isEmail(), body("password").exists(), async (req, res, next) => {
const result = validationResult(req)
if (result.isEmpty()) {
const { email, password } = matchedData(req);
const user = await prisma.member.findUnique({
where: {
email: email
}
})
if (user) {
if (password != user.password) {
const error = Error("Incorrect credentials.")
return next(error)
}
const token = jwt.sign({email: email}, process.env.TOKEN_SECRET as string, { expiresIn: "30m" })
res.cookie("token", token, {
secure: false,
httpOnly: false,
maxAge: 6 * 60 * 60 * 1000
}).status(200);
return res.json("Cookie has been set")
} else return null;
}
res.send({ errors: result.array() })
})
// dummy API route
app.get("/api", (req, res) => {
res.send("API Page")
})
// memberdata API route
app.get("/api/memberdata", async (req, res) => {
// begin token verification
// usually a separate function, but explicitly stated here for debugging
const token = req.cookies.token;
console.log(req.cookies);
if (!token) return res.status(401).json("You need to login!")
jwt.verify(token, process.env.TOKEN_SECRET as string, (err: any) => {
console.log(err);
if (err) return res.status(403);
})
// end token verification
const useremail = "[email protected]";
const memberdata = await prisma.member.findUnique({
where: {
email: useremail
},
select: {
social: true,
health: true,
philanthropy: true,
development: true,
fundraising: true,
events: true
}
});
return res.json(memberdata);
})
app.listen(port, () => {
console.log(`Express Server listening on port ${port}`);
});
当初始身份验证路由被调用时,cookie会在浏览器中成功设置。当/api/memberdata
路由被调用时,它根据第一个条件返回,因为它检测到req.cookies
是一个空对象。我尝试了以下方法:
- CORS中的
credentials: true
- 先调用
cookie-parser
中间件 - 在我的前端获取请求中包含
credentials: 'include'
(见下文)
以下是我的前端请求,供参考:
// initial authentication when the user logs in for the first time
async function onSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
const formDataJSON = JSON.stringify(Object.fromEntries(formData.entries()));
const response = await ( await fetch("http://localhost:5000/auth", {
method: 'POST',
credentials: 'include',
body: formDataJSON,
headers: {
"Content-Type": "application/json"
},
})).json()
}
// fetch to the api memberdata route
async function fetchData(): Promise<{
social: number,
health: number,
philanthropy: number,
development: number,
fundraising: number,
events: {title: string}[]
} | null > {
const response = await (await fetch("http://localhost:5000/api/memberdata", {
method: "GET",
cache: "no-store",
credentials: 'include',
headers: {
"Content-Type": "application/json"
},
})).json()
return response
}
更新1
在对这些请求进行了一番研究之后,我发现了以下几点:
让我们假设一个用户已经登录,并且令牌cookie被发送到浏览器。目前,如果用户尝试访问受身份验证保护的API路由,令牌验证器函数将返回,因为req. cookie对象为空。另请注意:
- 每当向
/api/memberdata
发送请求时,令牌cookie都会出现在开发人员工具的Network和Application选项卡中的请求标头下。
如果我从浏览器对API路由的请求中提取这个令牌,并将其插入到Postman GET请求中,该请求将毫无问题地通过验证器函数,并返回所请求的数据。
1条答案
按热度按时间qlvxas9a1#
从上面的评论中,听起来像是当您从浏览器向API直接请求
/auth
路由时,您正在向Next.js API route请求/api/memberdata
。Cookie将仅为您的Express服务器域
http://localhost:5000
设置。它们不会被包含在对本地Next.js API路由的请求中,而且,无论如何,您都不会将它们添加到Express中。最简单的解决方案是继续直接向Express API发出经过身份验证的请求。
所以你可能在客户端代码中有这样的东西.
改成这个
为了支持跨域Cookie,您需要配置Cookie选项以包含
SameSite
属性。对于客户端和服务器都运行在同一主机名(即
localhost
)上的开发环境,您可以使用“Lax”,这也是默认值。对于生产模式,您需要启用HTTPS的服务器和
SameSite=None; Secure
您可以将这些合并与
process.env.NODE_ENV
上的检查结合起来,以使代码更易于在任一环境中使用。