1. 코드 작성

// middlewares/auth-middleware.js

const jwt = require('jsonwebtoken');
const User = require('../schemas/user');
const Refresh = require('../schemas/refreshToken');
const dotenv = require('dotenv');

dotenv.config();

// refresh 토큰 유효성 검사
const validateRefreshToken = function (refreshToken) {
  try {
    jwt.verify(refreshToken, process.env.REFRESH_TOKEN);
    // 인증된 경우 True 반환
    return true;
  } catch (error) {
    // 만료된 경우 에러 발생, false 반환
    return false;
  }
};

// access 토큰 유효성 검사
const validateAccessToken = function (accessToken) {
  try {
    jwt.verify(accessToken, process.env.ACCESS_TOKEN);
    return true;
    // 인증된 경우 True 반환
  } catch (error) {
    // 만료된 경우 에러 발생, false 반환
    return false;
  }
};

const jwtValidation = async (req, res, next) => {
  try {
    const { accessToken, refreshToken } = req.cookies;
    // 1. 토큰 확인
    if (!refreshToken)
      return res
        .status(400)
        .json({ message: 'Refresh Token이 존재하지 않습니다.' });
    if (!accessToken)
      return res
        .status(400)
        .json({ message: 'Access Token이 존재하지 않습니다.' });

    // 2. 토큰 검증
    const isAccessTokenValidate = validateAccessToken(accessToken);
    const isRefreshTokenValidate = validateRefreshToken(refreshToken);

    // 3. Refresh 토큰이 만료된 경우
    if (!isRefreshTokenValidate) {
      return res
        .status(419)
        .json({ errorMessage: 'Refresh Token이 만료되었습니다.' });
    }
    // 4. AccessToken이 만료된 경우
    if (!isAccessTokenValidate) {
      // DB에서 Refresh 검증하기
      const findUser = await Refresh.findOne({ refreshToken });
      if (!findUser) {
        // 토큰 자체가 정상적이지만, 탈취를 당했거나 고의적으로 만료한 경우.
        return res.status(419).json({
          errorMessage: 'Refresh Token의 정보가 서버에 존재하지 않습니다.',
        });
      }
      // 새로운 AccessToken 생성
      const newAccessToken = jwt.sign(
        { userId: findUser.userId },
        process.env.ACCESS_TOKEN,
        { expiresIn: '10s' }
      );
      console.log('AccessToken 재발급 완료');
      // 새로운 AccessToken 전달 및 user 정보 전달
      res.cookie('accessToken', newAccessToken);
      const user = await User.findById(findUser.userId);
      res.locals.user = user;
      next();
    } else {
      // 5. 모든 경우가 정상적인 경우
      console.log('정상적인 경우');
      const findId = jwt.verify(accessToken, process.env.ACCESS_TOKEN);
      const user = await User.findById(findId.userId);
      res.locals.user = user;
      next();
    }
  } catch (err) {
    console.log(err);
    res.status(500).json({ errorMessage: '서버 에러' });
  }
};

module.exports = jwtValidation;
// router/auth.js

// 아이디 및 비밀번호 검사
const findAndValidate = async (email, password) => {
  try {
    const findUser = await User.findOne({ email });
    const validPassword = await bcrypt.compare(password, findUser.password);
    return findUser;
  } catch (err) {
    return false;
  }
};

// login 기능 구현
router.post('/', async (req, res) => {
  try {
    const { email, password } = req.body;

    const findUser = await findAndValidate(email, password);
    if (!findUser) {
      return res
        .status(400)
        .json({ errorMessage: '이메일이나 비밀번호가 올바르지 않습니다.' });
    }
    // access Token 생성
    const accessToken = jwt.sign(
      { userId: findUser.userId },
      process.env.ACCESS_TOKEN,
      { expiresIn: '10s' }
    );

    // refresh Token 생성
    const refreshToken = jwt.sign({}, process.env.REFRESH_TOKEN, {
      expiresIn: '7d',
    });

    const findRefreshToken = await Refresh.find({ userId: findUser.userId });
    // console.log(findRefreshToken);

    // Refresh DB에 userId가 있는 경우
    if (findRefreshToken) {
      await Refresh.updateOne(
        { userId: findUser.userId },
        { $set: { refreshToken } }
      );
    } else {
      // DB에 userId가 없는 경우
      await Refresh.create({ refreshToken, userId: findUser.userId });
    }

    res.cookie('accessToken', accessToken);
    res.cookie('refreshToken', refreshToken);
    res.status(200).json({ accessToken, refreshToken });
  } catch (err) {
    console.log(err);
    res.status(500).json({ errorMessage: '서버 에러' });
  }
});

1. Login 기능 구현

  1. findAndVaildate 이메일을 통해 email과 password 검증 진행
  2. accessToken과 Refresh 토큰 발급
  3. Refresh DB에서 데이터 확인

3.1 DB에 UserId가 있는 경우

3.2 DB에 userId가 없는 경우

2. Token Middleware 검증

  1. token이 제대로 있는지 검증 진행