Node.js

[Node.js] JWT(JSON Web Tokens) 로그인

셩리둥절 2022. 9. 28. 04:16
반응형

프로젝트 진행 시 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

.env file

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 해버리는 모습.... 얼른 기능을 끝내야 해서 얼렁뚱땅 해버린 험난한 로그인 구현....

지금이라도 수정하여 다행입니다...

반응형