๐ ๊ณต๋ถํ๋ ์ง์ง์ํ์นด๋ ์ฒ์์ด์ง?
[E-Commerce App with REST API] (6) cookie-parser & Refresh Token์ผ๋ก ์ฌ๋ฐ๊ธ ๋ฐ๊ธฐ (Access Token ์ฐจ์ด์ ) ๋ณธ๋ฌธ
[E-Commerce App with REST API] (6) cookie-parser & Refresh Token์ผ๋ก ์ฌ๋ฐ๊ธ ๋ฐ๊ธฐ (Access Token ์ฐจ์ด์ )
์ง์ง์ํ์นด 2023. 3. 23. 00:13<๋ณธ ๋ธ๋ก๊ทธ๋ Developers Corner ์ ์ ํ๋ธ๋ฅผ ์ฐธ๊ณ ํด์ ๊ณต๋ถํ๋ฉฐ ์์ฑํ์์ต๋๋ค :-)>
=> Node.js E-Commerce App with REST API: Let's Build a Real-Life Example!
๐ท login ์ cookie ์์ฑํ๊ธฐ
Cookie ํค๋๋ฅผ ํ์ฑํ๊ณ , ์ฟ ํค ์ด๋ฆ์ ์ํด ํค๊ฐ ์ง์ ๋ ๊ฐ์ฒด๋ก req.cookies๋ฅผ ์ฑ์
secret ๋ฌธ์์ด์ ์ ๋ฌํ์ฌ ์ ํ์ ์ผ๋ก ์๋ช
๋(signed) ์ฟ ํค ์ง์์ ํ์ฑํ
secret ๋ฌธ์์ด์ ๋ค๋ฅธ ๋ฏธ๋ค์จ์ด์์ ์ฌ์ฉํ ์ ์๋๋ก req.secret์ ํ ๋นํจ
npm i cookie-parser
๐ท JWT(JSON Web Token)
์ ์ ๋ฅผ ์ธ์ฆํ๊ณ ์๋ณํ๊ธฐ ์ํ ํ ํฐ
ํ ํฐ์ ์ธ์ ๊ณผ๋ ๋ฌ๋ฆฌ ์๋ฒ๊ฐ ์๋ ํด๋ผ์ด์ธํธ์ ์ ์ฅ
โ Refresh token
: ์๋ก์ด access token์ ์ฌ๋ฐ๊ธ ๋ฐ์ ์ ์๋ ์ ํจ ๊ธฐ๊ฐ์ด ๊ธด token
: ๊ธด ์ ํจ๊ธฐ๊ฐ์ ๊ฐ์ง๋ฉด์, Access Token์ด ๋ง๋ฃ๋์ ๋ ์๋ก ๋ฐ๊ธํด์ฃผ๋ ์ด์
โ Access Token
: ์ธ์ฆ์ ์ํ JWT์ด๋ฉด์, ๋์์ ๋ณด์์ ์ํด ์ ํจ๊ธฐ๊ฐ์ด ๋งค์ฐ ์งง๋ค
=> Access Token์ ์ ํจ๊ธฐ๊ฐ์ ์ค์ด๊ณ , Refresh Token์ด๋ผ๋ ์๋ก์ด ํ ํฐ์ ๋ฐ๊ธํจ์ผ๋ก์จ ํด๊ฒฐ
๐ ์๋ ๋ฐฉ๋ฒ
1) ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ์ ์๋
2) ์๋ฒ๋ DB์ ์ฌ์ฉ์๊ฐ ์ ์ฅ๋์ด ์๋์ง ํ์ธ
3) ์ฌ์ฉ์๊ฐ ์กด์ฌํ๋ฉด access token๊ณผ refresh token์ ๋ฐ๊ธ
4) ๋ฐ๊ธ๋ refresh token์ ์ฌ์ฉ์์ id์ Token ํ ์ด๋ธ์ ์ ์ฅ
5) ์๋ฒ๋ client์ access token๊ณผ refresh token์ ์๋ต
6) ์ฌ์ฉ์๊ฐ ์๋ฒ์ ์์ฒญ์ ํ๋ฉด, ์๋ฒ๋ access token์ ๊ฒ์ฆ ํ ์์ฒญํ ๋ฐ์ดํฐ๋ฅผ ์๋ต
7) Access Token์ด ๋ง๋ฃ๋ ํ ์ฌ์ฉ์๊ฐ ์๋ฒ์ ์์ฒญ์ ํ๊ฒ๋๋ฉด, access token์ ๋ง๋ฃ๋จ์ ์๋ต
8) Access token์ ๋ง๋ฃ๋ฅผ ํ์ธํ client๋ ์๋ฒ์ access token์ refresh๋ฅผ ์์ฒญ
9) ์๋ฒ๋ ๋ง๋ฃ๋ access token์์ ์ฌ์ฉ์์ id๋ฅผ ์ป์ ํ id๋ก DB์ refresh token์ด ์ ํจํ์ง ํ์ธ
10) refresh token์ด ์ ํจํ๋ค๋ฉด access token์ ์ฌ๋ฐ๊ธ, client์ ์ ์ก
11) refresh token๋ ๋ง๋ฃ๋์๋ค๋ฉด ์๋ก ๋ก๊ทธ์ธ
๐ท ์ฝ๋
โ controllers/userCtrl.js
const User = require("../models/User");
const bcrypt = require("bcrypt");
const asyncHandler = require("express-async-handler");
const { generateToken } = require("../config/jwtToken");
const { validateMongodbID } = require("../utils/validateMongodbID");
const { generateRefreshToken } = require("../config/refreshToken");
const jwt = require("jsonwebtoken");
// create a user
const createUser = asyncHandler(async (req, res) => {
const { firstname, lastname, email, mobile, password } = req.body;
const findUser = await User.findOne({ email: email });
// email์ด db์ ์๋ค๋ฉด
if (!findUser) {
// Create a new User
// 1) ์ฐ์ ๋น๋ฐ๋ฒํธ ํด์ฌํ(์ํธํ)
const hashedPassword = await bcrypt.hash(password, 10);
// 2) ์ User ์ ๋ณด ๋ง๋ค๊ธฐ
const newUser = await User.create({
firstname, lastname, email, mobile, password: hashedPassword
});
res.json(newUser);
} else {
// User already exists
throw new Error("User already exists");
}
});
// login a user
const loginCheck = asyncHandler(async (req, res) => {
const { email, password } = req.body;
// check if user exists or not
const findUser = await User.findOne({ email: email });
// user ๊ฐ ์๋ค๋ฉด done
if (!findUser) {
throw new Error("That email is not registered!");
}
// Match password (๊ธฐ์กด ๋น๋ฐ๋ฒํธ์ ์
๋ ฅํ ๋น๋ฐ๋ฒํธ ์ฒดํฌ)
bcrypt.compare(password, findUser.password, async (err, isMatch) => {
const refreshToken = await generateRefreshToken(findUser?._id);
const updateUser = await User.findByIdAndUpdate(
findUser._id, {
refreshToken: refreshToken,
}, {
new: true
}
);
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
maxAge: 72 * 60 * 60 * 1000,
});
if (isMatch) {
res.json({
_id: findUser?._id,
firstname: findUser?.firstname,
lastname: findUser?.lastname,
email: findUser?.email,
mobile: findUser?.mobile,
token: generateToken(findUser?._id)
});
} else {
throw new Error("Email or Password is incorrect!");
}
});
});
// Handle refresh token
const handleRefreshToken = asyncHandler(async (req, res) => {
try {
const cookie = req.cookies;
// refresh token ์์!
if (!cookie?.refreshToken) {
throw new Error("No Refresh Token in cookies");
}
// refresh token ์์ฑ!
const refreshToken = cookie.refreshToken;
const user = await User.findOne({ refreshToken });
if (!user) {
throw new Error("No Refresh token present in db or not matched");
}
// refresh token์ secret key ๊ธฐ๋ฐ์ผ๋ก ์์ฑ
jwt.verify(refreshToken, process.env.SECRET, (err, decoded) => {
if (err || user.id !== decoded.id) {
throw new Error("There is something wrong with refresh token");
}
// access Token ๋ฐ๊ธ
const accessToken = generateToken(user?._id);
res.json({ accessToken });
});
} catch (error) {
throw new Error(error);
}
});
// Update a user
const updateUser = asyncHandler(async (req, res) => {
const { _id } = req.user;
validateMongodbID(_id);
try {
const updateUser = await User.findByIdAndUpdate(
_id,
{
firstname: req?.body?.firstname,
lastname: req?.body?.lastname,
email: req?.body?.email,
mobile: req?.body?.mobile,
}, {
new: true,
}
);
res.json(updateUser);
} catch (error) {
throw new Error(error);
}
});
// get all users
const getAllUsers = asyncHandler(async (req, res) => {
try {
const getUser = await User.find();
res.json({ getUser });
} catch (error) {
throw new Error(error);
}
});
// get a single users
const getAUsers = asyncHandler(async (req, res) => {
const { id } = req.params;
validateMongodbID(id);
try {
const getUser = await User.findById(id);
res.json({ getUser });
} catch (error) {
throw new Error(error);
}
});
// delete a user
const deleteAUser = asyncHandler(async (req, res) => {
const { id } = req.params;
validateMongodbID(id);
try {
const deleteAUser = await User.findByIdAndDelete(id);
res.json({ deleteAUser });
} catch (error) {
throw new Error(error);
}
});
// block user
const blockUser = asyncHandler(async (req, res) => {
const { id } = req.params;
validateMongodbID(id);
try {
const block = await User.findByIdAndUpdate(
id,
{
isBlocked: true,
},
{
new: true,
}
);
res.json(block);
// res.json({
// message : "User blocked"
// });
} catch (error) {
throw new Error(error);
}
});
const unblockUser = asyncHandler(async (req, res) => {
const { id } = req.params;
validateMongodbID(id);
try {
const unblock = await User.findByIdAndUpdate(
id,
{
isBlocked: false,
},
{
new: true,
}
);
res.json({
message: "User unblocked"
});
} catch (error) {
throw new Error(error);
}
});
module.exports = {
createUser,
loginCheck,
getAllUsers,
getAUsers,
deleteAUser,
updateUser,
blockUser,
unblockUser,
handleRefreshToken
};
โ routes/authRoute.js
const express = require("express");
const router = express.Router();
const { createUser, loginCheck, getAllUsers, getAUsers, deleteAUser, updateUser, blockUser, unblockUser, handleRefreshToken } = require("../controllers/userCtrl");
const { authMiddleware, isAdmin } = require("../middlewares/authMiddleware");
router.post("/register", createUser);
router.post("/login", loginCheck);
router.get("/all-users", getAllUsers);
router.get("/refresh", handleRefreshToken);
router.get("/:id", authMiddleware, isAdmin, getAUsers);
router.delete("/:id", deleteAUser);
router.put("/edit-user", authMiddleware, updateUser);
router.put("/block-user/:id", authMiddleware, isAdmin, blockUser);
router.put("/unblock-user/:id", authMiddleware, isAdmin, unblockUser);
module.exports = router;
โ config/jwtToken.js
const jwt = require("jsonwebtoken");
const generateToken = (id) => {
return jwt.sign({id}, process.env.SECRET, {expiresIn : "1d"});
};
module.exports = {
generateToken
};