使用刷新令牌的 JWT 身份验证
在构建 Web 应用程序时,身份验证是重要方面之一,我们通常使用 JWT 令牌来实现身份验证(您可以在此处了解有关 JWT 的更多信息)。我们创建一个访问令牌并将其存储在本地存储或会话或 cookie 中。但是有一种更安全的方法可以使用刷新令牌来实现这一点。
Refresh Tokens:它是一个唯一的令牌,用于获取额外的访问令牌。这使您可以拥有短期访问令牌,而不必在每次过期时收集凭据。
由于安全原因,访问令牌在较长时间内无效,因此刷新令牌有助于在没有登录凭据的情况下重新验证用户。这个刷新令牌永远不会暴露给客户端 Javascript,即使我们的访问令牌被泄露,它也会在很短的时间内过期。因此,我们将发送两个令牌而不是一个,一个访问令牌和一个刷新令牌。访问令牌将包含所有用户信息并将存储在 Javascript 运行时中,但刷新令牌将安全地存储在仅 HTTP 的 cookie 中。
Auth Persistence:我们可以轻松地在刷新和登录之间保持用户,而无需任何凭据。我们可以创建一个名为 refresh 的新路由,每当令牌过期或用户刷新时,我们都可以通过向该路由发送请求来获取新的访问令牌
实现:现在让我们使用 JWT 和 Refresh 令牌实现身份验证。我们将首先创建一个新的 Express 应用程序并安装所有必需的依赖项。
第一步:运行以下命令 初始化项目并创建一个索引文件和环境文件。 (确保安装了 node 和 npm)
npm init -y
touch index.js .env
第 2 步:安装所有必需的依赖项并在代码编辑器中打开项目。
npm install express cookie-parser dontenv jsonwebtoken
项目结构:
我们将在 Index.js 中编写我们的身份验证逻辑。我们将首先创建一个快速应用程序,然后实现两个路由login和refresh 。登录路由将收到一个发布请求,然后它会检查凭据是否匹配它会发送一个刷新令牌和访问令牌作为响应。刷新路由还会收到一个发布请求,它将验证刷新令牌,如果正确,则会使用新的访问令牌进行响应,否则会引发授权错误。
示例:我们现在将实现两个路由登录和刷新。以下代码适用于 index.js:
const dotenv = require('dotenv');
const express = require('express');
const cookieparser = require('cookie-parser');
const jwt = require('jsonwebtoken')
const bodyParser = require('body-parser');
// Configuring dotenv
dotenv.config();
const app = express();
// Setting up middlewares to parse request body and cookies
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieparser());
const userCredentials = {
username: 'admin',
password: 'admin123',
email: 'admin@gmail.com'
}
app.post('/login', (req, res) => {
// Destructuring username & password from body
const { username, password } = req.body;
// Checking if credentials match
if (username === userCredentials.username &&
password === userCredentials.password) {
//creating a access token
const accessToken = jwt.sign({
username: userCredentials.username,
email: userCredentials.email
}, process.env.ACCESS_TOKEN_SECRET, {
expiresIn: '10m'
});
// Creating refresh token not that expiry of refresh
//token is greater than the access token
const refreshToken = jwt.sign({
username: userCredentials.username,
}, process.env.REFRESH_TOKEN_SECRET, { expiresIn: '1d' });
// Assigning refresh token in http-only cookie
res.cookie('jwt', refreshToken, { httpOnly: true,
sameSite: 'None', secure: true,
maxAge: 24 * 60 * 60 * 1000 });
return res.json({ accessToken });
}
else {
// Return unauthorized error if credentials dont match
return res.status(406).json({
message: 'Invalid credentials' });
}
})
app.post('/refresh', (req, res) => {
if (req.cookies?.jwt) {
// Destructuring refreshToken from cookie
const refreshToken = req.cookies.jwt;
// Verifying refresh token
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET,
(err, decoded) => {
if (err) {
// Wrong Refesh Token
return res.status(406).json({ message: 'Unauthorized' });
}
else {
// Correct token we send a new access token
const accessToken = jwt.sign({
username: userCredentials.username,
email: userCredentials.email
}, process.env.ACCESS_TOKEN_SECRET, {
expiresIn: '10m'
});
return res.json({ accessToken });
}
})
} else {
return res.status(406).json({ message: 'Unauthorized' });
}
})
app.listen(process.env.PORT, () => {
console.log(`Server active on http://localhost:${process.env.PORT}!`);
})