로그인을 구현하는 서버를 의사코드와 함께 작성했다.
알고가야할점 👀
1. 유저가 로그인 하면 db에서 유저를 찾는다
2. 유저가 있을 경우 : userinfo.js에서 설정해준다.
3. 유저가 없을 경우 : login.js에서 설정해준다.
유저가 없을 경우엔 바로 return 문으로 "not authorized" 반환해준다.
//login.js
const { USER_DATA } = require("../../db/data");
const { generateToken } = require("../helper/tokenFunctions");
module.exports = async (req, res) => {
const { userId, password } = req.body.loginInfo;
const { checkedKeepLogin } = req.body;
const userInfo = {
...USER_DATA.find(
(user) => user.userId === userId && user.password === password
),
};
//1 user없음
if (!userInfo.id) return res.status(401).send("Not Authorized"); //얼리리턴. 찾으면 리턴해줌. 다음 내용 로그인하면 보여줄필요가 없으니까
//2 구조분해할당으로 각각 변수 설정해줌
const { accessToken, refreshToken } = generateToken(
userInfo,
checkedKeepLogin
);
//쿠키옵션설정
const cookiesOption = {
domain: "localhost",
path: "/",
httpOnly: true,
sameSite: "none",
secure: true,
};
//access토큰을 갖는 쿠키
res.cookie("access_jwt", accessToken, cookiesOption);
//refresh 토큰이 있을때
if (refreshToken) {
//기간만료를 설정해줌
cookiesOption.maxAge = 1000 * 60 * 24;
res.cookie("refresh_jwt", refreshToken, cookiesOption);
}
//refreshtoken이 검증된 토큰이고, 다시 accesstoken을 발급해주고난 후, 다시 accessToken인지 검증받게하려고 상태를 위로 올림
res.status(302).redirect("/userinfo");
};
- 엑세스 토큰이 있다면 -> 유저 정보를 찾는다
- 비밀번호 정보는 민감하니 삭제하고 user정보를 보내준다.
- 1. 엑세스 토큰이 없으면 리프레시 토큰을 검증한다.
- 1) 리프레시 토큰을 검증에 실패했다면 로그인 실패로 반환해준다.
- 2) 리프레시 토큰이 있다면 유저를 찾고
- 1+ 유저가 없다면 "Not Found"
- 2+ 유저가 있다면 액세스 토큰을 갱신해준다.
액세스 토큰과 리프레시 토큰이 없다면 "NOT Authorized" 반환
//userinfo..js
const { USER_DATA } = require("../../db/data");
const { verifyToken, generateToken } = require("../helper/tokenFunctions");
module.exports = async (req, res) => {
const { access_jwt, refresh_jwt } = req.cookies;
//req.cookies.access_jwt
//액세스토큰이 있다면
if (access_jwt) {
const accessTokenPayLoad = verifyToken("access", access_jwt);
//액세스 토큰이 유효하다면
if (accessTokenPayLoad) {
//유저정보 찾고
const userInfo = {
...USER_DATA.find((user) => user.id === accessTokenPayLoad.id),
};
//비밀번호 삭제후에 user정보 보내준다
delete userInfo.password;
return res.send(userInfo);
}
//리프레쉬 토큰 있는지 없는지 검사
//1. 리프레쉬 토큰이 있을때 -> 검증해보고 맞다면 유저를 찾고,
//유저가 있을때는 엑세스 토큰을 갱신
//유저가 없다면 유저가 없다고 반환
if (refresh_jwt) {
//리프레쉬 토큰 검증
//밑의 변수는 verifyToken으로 리프레쉬 토큰을 검증해주는 함수를 저장
const refreshTokenPayLoad = verifyToken("refresh", refresh_jwt);
//만약 리프레쉬 토큰 검증에 실패했다면 반환값 (단호)
if (!refreshTokenPayLoad) return res.status(401).send("Not Authorized");
//유저를 찾고
const exUser = {
...USER_DATA.find((user) => user.id === refreshTokenPayLoad.id),
};
//유저가 없다면
if (!exUser) return res.status(404).send("Not Found");
//유저가 있다면 액세스 토큰 갱신
const cookiesOption = {
domain: "localhost",
path: "/",
httpOnly: true,
sameSite: "none",
secure: true,
};
res.cookie("access_jwt", generateToken(exUser, false), cookiesOption);
return res.send(exUser);
}
}
return res.status(401).send("Not Authorized");
};
clearCoockie로 access,refresh토큰을 지워주고, 205상태와 함께 "logout"을 보내준다.
//logout.js
module.exports = (req, res) => {
const cookiesOption = {
domain: "localhost",
path: "/",
httpOnly: true,
sameSite: "none",
secure: true,
};
res.clearCookie("access_jwt", cookiesOption);
res.clearCookie("refresh_jwt, cookiesOption");
res.status(205).send("logout");
};
위 코드들의 함수들 중 verifyToken, generateToken
을 이해하는 것이 중요한데, 코드를 보면서 파악하면 이해하기 쉽다.
//tokenFunction.js
require("dotenv").config();
const { sign, verify } = require("jsonwebtoken");
module.exports = {
generateToken: (user, checkedKeepLogin) => {
const payload = {
id: user.id,
email: user.email,
};
let result = {
accessToken: sign(payload, process.env.ACCESS_SECRET, {
//jwt를 반환한다
expiresIn: "1d", // 1일간 유효한 토큰을 발행합니다.
}),
};
if (checkedKeepLogin) {
result.refreshToken = sign(payload, process.env.REFRESH_SECRET, {
expiresIn: "7d", // 일주일간 유효한 토큰을 발행합니다.
});
}
return result;
},
verifyToken: (type, token) => {
let secretKey, decoded;
switch (type) {
case "access":
secretKey = process.env.ACCESS_SECRET;
break;
case "refresh":
secretKey = process.env.REFRESH_SECRET;
break;
default:
return null;
}
try {
decoded = verify(token, secretKey);
} catch (err) {
console.log(`JWT Error: ${err.message}`);
return null;
}
return decoded;
},
};