虽然 JWT 是一种非常流行的认证方式,受到很多人的喜爱。大多数人最终将其存储在本地存储中。我不会在这里争论什么是在前端存储 jwt 的最佳方式,这不是我的意图。
如果您已经阅读了我创建的这篇关于如何使用 JWT 创建简单的身份验证和授权系统的文章,您一定已经注意到,当从登录路由发出 http 请求时,我会发送 jwt 作为响应。也就是说,这个想法是将其保存在本地存储中。
但是,还有其他方法可以将 jwt 发送到前端,今天我将教你如何将 jwt 存储在 cookie 中。
为什么要使用 cookie?
有时我有点懒惰,因此我不想在向 Api 发出请求时不断地在标头中发送 jwt。这就是 cookie 的用武之地,您可以在每次发出 http 请求时发送它们而无需担心。
另一个原因是,如果您使用 localstorage,在前端,您必须确保在用户注销时从 localstorage 中删除 jwt。使用 cookie 时,您只需要 api 中的路由来发出 http 请求以删除您在前端拥有的 cookie。
首选使用 cookie 的原因有很多,这里我给出了一些在项目细化过程中可能出现的表面上的小例子。
现在我们有了一个大致的想法,让我们来编码吧!
让我们编码
首先,我们将安装以下依赖项:
npm install express jsonwebtoken cookie-parser
现在只需创建一个简单的 Api:
const express = require("express");
const app = express();
app.get("/", (req, res) => {
return res.json({ message: "Hello World 🇵🇹 🤘" });
});
const start = (port) => {
try {
app.listen(port, () => {
console.log(`Api up and running at: http://localhost:${port}`);
});
} catch (error) {
console.error(error);
process.exit();
}
};
start(3333);
正如您可能已经猜到的那样,我们需要一些能够在我们的 Api 中使用 cookie 的东西,这就是cookie-parser 的用武之地。
首先我们将导入它并在我们的中间件中注册它。
const express = require("express");
const cookieParser = require("cookie-parser");
const app = express();
app.use(cookieParser());
//Hidden for simplicity
现在我们准备开始在我们的 Api 中创建一些路由。
我们要创建的第一个路由是登录路由。首先,我们将创建我们的 jwt,然后将其存储在一个名为“access_token”的 cookie 中。cookie 会有一些选项,例如 httpOnly(在应用程序开发过程中使用)和 secure(在生产环境中使用,带有 https)。
然后我们会发送一个回复说我们已经成功登录了。
app.get("/login", (req, res) => {
const token = jwt.sign({ id: 7, role: "captain" }, "YOUR_SECRET_KEY");
return res
.cookie("access_token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
})
.status(200)
.json({ message: "Logged in successfully 😊 👌" });
});
现在登录完成,让我们检查我们是否在客户端中收到了带有 jwt 的 cookie,在这种情况下我使用了Insomnia。
现在完成身份验证,让我们进行授权。为此,我们必须创建一个中间件来检查我们是否有 cookie。
const authorization = (req, res, next) => {
// Logic goes here
};
现在我们必须检查我们是否有名为“access_token”的cookie,如果没有,那么我们将禁止访问控制器。
const authorization = (req, res, next) => {
const token = req.cookies.access_token;
if (!token) {
return res.sendStatus(403);
}
// Even more logic goes here
};
如果我们有 cookie,我们将验证令牌以获取数据。但是,如果发生错误,我们将禁止访问控制器。
const authorization = (req, res, next) => {
const token = req.cookies.access_token;
if (!token) {
return res.sendStatus(403);
}
try {
const data = jwt.verify(token, "YOUR_SECRET_KEY");
// Almost done
} catch {
return res.sendStatus(403);
}
};
现在是时候在请求对象中声明新属性,以便我们更轻松地访问令牌的数据。
为此,我们将创建req.userId并分配令牌中 id 的值。我们还将创建req.userRole并分配令牌中存在的角色的值。然后只需授予对控制器的访问权限。
const authorization = (req, res, next) => {
const token = req.cookies.access_token;
if (!token) {
return res.sendStatus(403);
}
try {
const data = jwt.verify(token, "YOUR_SECRET_KEY");
req.userId = data.id;
req.userRole = data.role;
return next();
} catch {
return res.sendStatus(403);
}
};
现在我们要创建一个新的路由,这次我们要创建注销的路由。基本上,我们将从 cookie 中删除值。也就是说,我们将删除 jwt。
但是,我们想将授权中间件添加到我们的新路由中。这是因为如果用户有 cookie,我们想注销。如果用户有 cookie,我们将删除它的值并发送一条消息说用户已成功注销。
app.get("/logout", authorization, (req, res) => {
return res
.clearCookie("access_token")
.status(200)
.json({ message: "Successfully logged out 😏 🍀" });
});
所以现在让我们测试我们是否可以注销。目的是验证第一次注销时,我们会有一条消息说它是成功的。但是当我们在没有cookie的情况下再次测试时,我们必须有一个错误说它是被禁止的。
现在我们只需要创建最后一个路由,以便我们可以从 jwt 获取数据。只有当我们可以访问 cookie 中的 jwt 时,才能访问此路由。如果我们不这样做,我们将得到一个错误。现在我们将能够使用我们添加到请求中的新属性。
app.get("/protected", authorization, (req, res) => {
return res.json({ user: { id: req.userId, role: req.userRole } });
});
如果我们在我们最喜欢的客户端上测试它。我们将首先测试整个工作流程。遵循以下几点:
- 登录获取cookie;
- 访问受保护的路由查看jwt数据;
- 注销以清除cookie;
- 再次访问受保护的路由,但这次我们预计会出错。
我在这里留下一个 gif 来展示最终结果应该如何预期:
最终代码必须如下:
const express = require("express");
const cookieParser = require("cookie-parser");
const jwt = require("jsonwebtoken");
const app = express();
app.use(cookieParser());
const authorization = (req, res, next) => {
const token = req.cookies.access_token;
if (!token) {
return res.sendStatus(403);
}
try {
const data = jwt.verify(token, "YOUR_SECRET_KEY");
req.userId = data.id;
req.userRole = data.role;
return next();
} catch {
return res.sendStatus(403);
}
};
app.get("/", (req, res) => {
return res.json({ message: "Hello World 🇵🇹 🤘" });
});
app.get("/login", (req, res) => {
const token = jwt.sign({ id: 7, role: "captain" }, "YOUR_SECRET_KEY");
return res
.cookie("access_token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
})
.status(200)
.json({ message: "Logged in successfully 😊 👌" });
});
app.get("/protected", authorization, (req, res) => {
return res.json({ user: { id: req.userId, role: req.userRole } });
});
app.get("/logout", authorization, (req, res) => {
return res
.clearCookie("access_token")
.status(200)
.json({ message: "Successfully logged out 😏 🍀" });
});
const start = (port) => {
try {
app.listen(port, () => {
console.log(`Api up and running at: http://localhost:${port}`);
});
} catch (error) {
console.error(error);
process.exit();
}
};
start(3333);
最后的笔记
显然,这个例子很简单,我不会不推荐阅读更多关于这个主题的内容。但我希望我能帮助解决您的任何疑问。
那你呢?
您是否使用或阅读过此身份验证策略?