프로젝트 진행 시 Front에서 토큰을 decode하는 방식으로 구현을 마무리 한 것이 아쉬워
백엔드에서 cookie 사용으로 로그인 기능을 수정하려고 합니다.
서적 '리액트를 다루는 기술' JWT 로그인을 참고하여 Express를 이용하여 수정했습니다!
기존 코드
exports.login = async (req, res) => {
const id = req.id;
const pwd = req.password;
const connection = getConnection();
const member = await (MemberRepository.selectMemberById(connection, id));
const memberNo = member.memberNo;
if (member == null) {
// user does not exist
throw new Error('login failed')
} else if (member !== null) {
const DBPwd = member.memberPwd;
const match = await bcrypt.compare(pwd, DBPwd);
//첫번째 매개변수가 사용자가 입력한 패스워드
// respond the token
if (match === true) {
const newToken = await generateToken
await console.log('token :', newToken);
return newToken
}
}
}
수정 후
1. client에게 id와 pwd를 받아와 Rest API 요청
export async function loginAPI(id, password) {
const data = {
id: id,
pwd: password
}
return await axios.post('http://localhost:8888/auths/login', data)
.then((res)=> {
console.log(res.data);
})
}
2. Router로 받아줍니다.
app.js
const express = require('express');
const app = express();
const PORT = 8888;
const authRouter = require('./src/routes/auth-routes');
app.use('/auths', authRouter);
app.listen(PORT, () => console.log('listening on port 8888...'));
auth-routes.js
const express = require('express');
const router = express.Router();
const authCtrl = require('../controllers/auth/auth.ctrl')
router.post('/login', authCtrl.login);
module.exports = router;
3. auth.ctrl.js
로그인 코드 작성
const getConnection = require('../../database/connection')
const MemberRepository = require('../../repositories/member/member-repo')
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
require("dotenv").config();
exports.login = async(req, res) => {
const { id, pwd } = req.body;
const connection = getConnection(); //db connection
//id, password request에 없으면 에러 처리
if(!id || !pwd) {
return res.status(401).json({
message: '아이디, 패스워드가 존재하지 않습니다.'
});
}
try {
const user = await (MemberRepository.selectMemberById(connection, id)); //user 정보 조회
//user가 존재하지 않을 시
if(!user) {
return res.status(401).json({
message: '사용자가 존재하지 않습니다.'
});
}
const match = await checkPassword(user, pwd);
//잘못된 비밀번호
if(match === false) {
return res.status(401).json({
message: '비밀번호 오류'
});
}
const accessToken = generateToken(user);
res.cookie('access_token', accessToken, {
maxAge: 1000 * 60 * 60 * 24 * 7, //7day
httpOnly: true,
});
delete user.memberPwd;
return res.status(200).json({
message: '로그인 성공',
user: user
})
} catch (e) {
console.log(e);
throw new Error('잘못된 로그인');
}
}
function generateToken(user) {
const accessToken = jwt.sign({
type: "JWT",
no: user.memberNo,
id: user.memberId,
nickname: user.nickname,
enrollDate: user.enrollDate,
memberRole: user.memberRole
},
process.env.ACCESS_TOKEN_SECRET,
{
expiresIn: '7d',
issuer: 'today'
});
return accessToken;
}
.env
Postman Headers Set-Cookie 확인
4. 토큰 검증 middleware 작성
const jwt = require('jsonwebtoken');
const jwtMiddleware = (req, res, next) => {
const token = req.cookies;
console.log('jwtMiddleware 실행');
console.log(token);
if(!token) return next();
try {
const decoded = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET);
console.log(decoded);
res.send({
message: 'token 확인 성공',
user: decoded
})
return next();
} catch (e) {
return next();
}
};
module.exports = jwtMiddleware;
req.cookies 여기서 문제가 발생했습니다.
Problem1. [Object: null prototype] {}
최상단의 app.js 파일에 app.use(cookieParser()) 을 했는데도 req.cookies가 [Object: null prototype] {} 로 뜨는 문제가 발생했습니다....분명 Postman으로 했을 때 잘 담겼는데! header에도 잘 담겨있는데!
열심히 찾아본 결과... cors 문제로 Front에서 API 요청시 백엔드와의 포트가 다르면 쿠키를 보내주지 못하는 문제라고 합니다.
https://biio-studying.tistory.com/238
해결 방법
Front 쪽 package.json 파일에 proxy를 추가하여 Front 포트를 가상으로 Back과 동일하게 만듭니다.
이렇게 요청하던 것을
이제 이렇게 요청해도 백엔드와 연결이 됩니다.
Chrome - 개발자도구 - application - cookie 확인 시 밑에와 같이 잘 담겨있는 것을 확인할 수 있습니다.
(프로젝트 기간 당시 이걸 몰라서 7일동안 울며 겨자먹기로 Front로 token 넘겨서 했던 것을 생각하면 눈물이 앞을 가립니다....💦🤣)
5. token exp이 3.5일인 경우 token 재발급
jwtMiddleware token exp 계산 후 3.5일 미만으로 남았을 시 token 재발급 코드 추가
const jwt = require('jsonwebtoken');
const MemberRepository = require('../repositories/member/member-repo');
const jwtMiddleware = (req, res, next) => {
const token = req.cookies.access_token;
console.log('jwtMiddleware 실행');
console.log(token);
if(!token) return next();
try {
const decoded = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET);
console.log(decoded);
res.send({
message: 'token 확인 성공',
user: decoded
})
const now = Math.floor(Date.now() / 1000);
if(decoded.exp - now < 60 * 60 * 24 * 3.5) {
const user = await (MemberRepository.selectMemberById(connection, id));
const token = generateToken(user);
res.cookie('access_token', accessToken, {
maxAge: 1000 * 60 * 60 * 24 * 7 //7일
httpOnly: true,
});
}
return next();
} catch (e) {
return next();
}
};
function generateToken(user) {
const accessToken = jwt.sign({
type: "JWT",
no: user.memberNo,
id: user.memberId,
nickname: user.nickname,
enrollDate: user.enrollDate,
memberRole: user.memberRole
},
process.env.ACCESS_TOKEN_SECRET,
{
expiresIn: '7d',
issuer: 'today'
});
return accessToken;
}
module.exports = jwtMiddleware;
++ 이전 코드 : Front에서 냅다 쿠키 set 해버리는 모습.... 얼른 기능을 끝내야 해서 얼렁뚱땅 해버린 험난한 로그인 구현....
지금이라도 수정하여 다행입니다...
'Node.js' 카테고리의 다른 글
[Node.js] [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client (0) | 2022.10.03 |
---|---|
[Node.js 교과서] 서버로서의 노드 (0) | 2022.09.27 |
[Node.js 교과서] 1. 노드 시작하기 (0) | 2022.09.27 |