Node JS에서 액세스 토큰 및 새로 고침 토큰을 사용한 JWT 인증
56132 단어 reactjavascriptjwtnode
그럼 코딩을 시작해 볼까요?
더 나은 이해를 위해 데모 비디오를 시청하는 것이 좋습니다. 내 작업이 마음에 든다면 내 채널을 지원해 주세요.
데모 비디오
Project Github Link
다음 표는 내보낸 Rest API의 개요를 보여줍니다.
행동 양식
URL
행위
게시하다
/가입하기
사용자 가입
게시하다
/로그인
로그인 사용자
게시하다
/새로 고침 토큰
새 액세스 토큰 받기
삭제
/새로 고침 토큰
로그아웃 사용자
새로 고침 토큰이란 무엇입니까?
Refresh Token은 Access Token에 불과하지만 1~2개월 정도의 수명을 가지고 있습니다. 액세스 토큰의 만료 시간은 약 10~15분입니다. 이 액세스 토큰이 만료될 때마다. 우리는 새 액세스 토큰을 얻기 위해 사용자에게 다시 로그인하도록 요청하지 않고 대신 서버에 새로 고침 토큰을 보냅니다. 여기에서 해당 토큰을 확인하고 새 액세스 토큰을 클라이언트에 보냅니다. 이 방법을 사용하면 사용자는 반복해서 로그인할 필요가 없습니다. 이것은 사용자 경험을 사용자에게 훨씬 더 쉽게 만듭니다.
Node.js 앱 만들기
$ mkdir refreshTokenAuth
$ cd refreshTokenAuth
$ npm init --yes
$ npm install express mongoose jsonwebtoken dotenv bcrypt joi joi-password-complexity
$ npm install --save-dev nodemon
프로젝트 구조
패키지.json
{
"name": "refreshTokenAuth",
"version": "1.0.0",
"description": "",
"main": "server.js",
"type": "module",
"scripts": {
"start": "nodemon server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^5.0.1",
"dotenv": "^16.0.0",
"express": "^4.17.3",
"joi": "^17.6.0",
"joi-password-complexity": "^5.1.0",
"jsonwebtoken": "^8.5.1",
"mongoose": "^6.2.8"
},
"devDependencies": {
"nodemon": "^2.0.15"
}
}
사용자 모델
/모델/User.js
import mongoose from "mongoose";
const Schema = mongoose.Schema;
const userSchema = new Schema({
userName: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
roles: {
type: [String],
enum: ["user", "admin", "super_admin"],
default: ["user"],
},
});
const User = mongoose.model("User", userSchema);
export default User;
사용자 토큰 모델
/models/UserToken.js
import mongoose from "mongoose";
const Schema = mongoose.Schema;
const userTokenSchema = new Schema({
userId: { type: Schema.Types.ObjectId, required: true },
token: { type: String, required: true },
createdAt: { type: Date, default: Date.now, expires: 30 * 86400 }, // 30 days
});
const UserToken = mongoose.model("UserToken", userTokenSchema);
export default UserToken;
토큰 생성 기능
/utils/generateTokens.js
import jwt from "jsonwebtoken";
import UserToken from "../models/UserToken.js";
const generateTokens = async (user) => {
try {
const payload = { _id: user._id, roles: user.roles };
const accessToken = jwt.sign(
payload,
process.env.ACCESS_TOKEN_PRIVATE_KEY,
{ expiresIn: "14m" }
);
const refreshToken = jwt.sign(
payload,
process.env.REFRESH_TOKEN_PRIVATE_KEY,
{ expiresIn: "30d" }
);
const userToken = await UserToken.findOne({ userId: user._id });
if (userToken) await userToken.remove();
await new UserToken({ userId: user._id, token: refreshToken }).save();
return Promise.resolve({ accessToken, refreshToken });
} catch (err) {
return Promise.reject(err);
}
};
export default generateTokens;
새로 고침 토큰 기능 확인
/utils/verifyRefreshToken.js
import UserToken from "../models/UserToken.js";
import jwt from "jsonwebtoken";
const verifyRefreshToken = (refreshToken) => {
const privateKey = process.env.REFRESH_TOKEN_PRIVATE_KEY;
return new Promise((resolve, reject) => {
UserToken.findOne({ token: refreshToken }, (err, doc) => {
if (!doc)
return reject({ error: true, message: "Invalid refresh token" });
jwt.verify(refreshToken, privateKey, (err, tokenDetails) => {
if (err)
return reject({ error: true, message: "Invalid refresh token" });
resolve({
tokenDetails,
error: false,
message: "Valid refresh token",
});
});
});
});
};
export default verifyRefreshToken;
검증 스키마 기능
/utils/validationSchema.js
import Joi from "joi";
import passwordComplexity from "joi-password-complexity";
const signUpBodyValidation = (body) => {
const schema = Joi.object({
userName: Joi.string().required().label("User Name"),
email: Joi.string().email().required().label("Email"),
password: passwordComplexity().required().label("Password"),
});
return schema.validate(body);
};
const logInBodyValidation = (body) => {
const schema = Joi.object({
email: Joi.string().email().required().label("Email"),
password: Joi.string().required().label("Password"),
});
return schema.validate(body);
};
const refreshTokenBodyValidation = (body) => {
const schema = Joi.object({
refreshToken: Joi.string().required().label("Refresh Token"),
});
return schema.validate(body);
};
export {
signUpBodyValidation,
logInBodyValidation,
refreshTokenBodyValidation,
};
인증 경로
/routes/auth.js
import { Router } from "express";
import User from "../models/User.js";
import bcrypt from "bcrypt";
import generateTokens from "../utils/generateTokens.js";
import {
signUpBodyValidation,
logInBodyValidation,
} from "../utils/validationSchema.js";
const router = Router();
// signup
router.post("/signUp", async (req, res) => {
try {
const { error } = signUpBodyValidation(req.body);
if (error)
return res
.status(400)
.json({ error: true, message: error.details[0].message });
const user = await User.findOne({ email: req.body.email });
if (user)
return res
.status(400)
.json({ error: true, message: "User with given email already exist" });
const salt = await bcrypt.genSalt(Number(process.env.SALT));
const hashPassword = await bcrypt.hash(req.body.password, salt);
await new User({ ...req.body, password: hashPassword }).save();
res
.status(201)
.json({ error: false, message: "Account created sucessfully" });
} catch (err) {
console.log(err);
res.status(500).json({ error: true, message: "Internal Server Error" });
}
});
// login
router.post("/logIn", async (req, res) => {
try {
const { error } = logInBodyValidation(req.body);
if (error)
return res
.status(400)
.json({ error: true, message: error.details[0].message });
const user = await User.findOne({ email: req.body.email });
if (!user)
return res
.status(401)
.json({ error: true, message: "Invalid email or password" });
const verifiedPassword = await bcrypt.compare(
req.body.password,
user.password
);
if (!verifiedPassword)
return res
.status(401)
.json({ error: true, message: "Invalid email or password" });
const { accessToken, refreshToken } = await generateTokens(user);
res.status(200).json({
error: false,
accessToken,
refreshToken,
message: "Logged in sucessfully",
});
} catch (err) {
console.log(err);
res.status(500).json({ error: true, message: "Internal Server Error" });
}
});
export default router;
새로 고침 토큰 경로
/routes/refreshToken.js
import { Router } from "express";
import UserToken from "../models/UserToken.js";
import jwt from "jsonwebtoken";
import { refreshTokenBodyValidation } from "../utils/validationSchema.js";
import verifyRefreshToken from "../utils/verifyRefreshToken.js";
const router = Router();
// get new access token
router.post("/", async (req, res) => {
const { error } = refreshTokenBodyValidation(req.body);
if (error)
return res
.status(400)
.json({ error: true, message: error.details[0].message });
verifyRefreshToken(req.body.refreshToken)
.then(({ tokenDetails }) => {
const payload = { _id: tokenDetails._id, roles: tokenDetails.roles };
const accessToken = jwt.sign(
payload,
process.env.ACCESS_TOKEN_PRIVATE_KEY,
{ expiresIn: "14m" }
);
res.status(200).json({
error: false,
accessToken,
message: "Access token created successfully",
});
})
.catch((err) => res.status(400).json(err));
});
// logout
router.delete("/", async (req, res) => {
try {
const { error } = refreshTokenBodyValidation(req.body);
if (error)
return res
.status(400)
.json({ error: true, message: error.details[0].message });
const userToken = await UserToken.findOne({ token: req.body.refreshToken });
if (!userToken)
return res
.status(200)
.json({ error: false, message: "Logged Out Sucessfully" });
await userToken.remove();
res.status(200).json({ error: false, message: "Logged Out Sucessfully" });
} catch (err) {
console.log(err);
res.status(500).json({ error: true, message: "Internal Server Error" });
}
});
export default router;
.env 파일
/.env
DB = Your database URL
SALT = 10
ACCESS_TOKEN_PRIVATE_KEY = Add your private key
REFRESH_TOKEN_PRIVATE_KEY = Add your private key
데이터베이스 연결
/dbConnect.js
import mongoose from "mongoose";
const dbConnect = () => {
const connectionParams = { useNewUrlParser: true };
mongoose.connect(process.env.DB, connectionParams);
mongoose.connection.on("connected", () => {
console.log("Connected to database sucessfully");
});
mongoose.connection.on("error", (err) => {
console.log("Error while connecting to database :" + err);
});
mongoose.connection.on("disconnected", () => {
console.log("Mongodb connection disconnected");
});
};
export default dbConnect;
Sever.js
/server.js
import express from "express";
import { config } from "dotenv";
import dbConnect from "./dbConnect.js";
import authRoutes from "./routes/auth.js";
import refreshTokenRoutes from "./routes/refreshToken.js";
const app = express();
config();
dbConnect();
app.use(express.json());
app.use("/api", authRoutes);
app.use("/api/refreshToken", refreshTokenRoutes);
const port = process.env.PORT || 8080;
app.listen(port, () => console.log(`Listening on port ${port}...`));
이것이 우리가 Node JS에서 새로 고침 및 액세스 토큰 기반 인증을 성공적으로 구현한 것입니다.
이 프로젝트의 보너스를 위해 인증된 사용자만 액세스할 수 있는 경로와 역할 기반 인증을 구현했습니다. Demo Video에서 찾을 수 있습니다.
감사합니다 :)
Reference
이 문제에 관하여(Node JS에서 액세스 토큰 및 새로 고침 토큰을 사용한 JWT 인증), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/cyberwolve/jwt-authentication-with-access-tokens-refresh-tokens-in-node-js-5aa9텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)