API : 로그인 된 유저만 접근하도록, 미들웨어에서 처리하기




이번 고민은?

movester는 관리자 페이지인 backoffice와 사용자 페이지인 product 두 개의 웹사이트가 존재한다.

backoffice는 모든 api가 관리자 계정으로 로그인되어 있어야만 사용할 수 있어야 하기 때문에,

모든 api에서 로그인 된 유저가 보낸 api인지를 확인해야 한다.

로그인 과정에서 accessToken과 refreshToken을 쿠키에 담아 보내줬기 때문에,

api를 요청한 사람의 쿠키에서 해당 토큰들을 살펴보면 된다.

그렇다면 여기서 고민이 생긴다.

해당 과정을 어디서 처리하지?

api 사용 권한 체크를

  1. 각 컨트롤러에서 처리할까?
  2. 미들웨어에서 권한 체크를 먼저 해주고 api 로직으로 옮겨줄까?


선택한 방식

미들웨어에서 처리해주고 권한이 없다면 api 실패, 권한 있으면 해당 api 로직으로 이동(컨트롤러 호출)로 결정하였다.


선택한 이유

  1. backoffice 처럼 모든 api가 해당 로직을 거쳐야하는 상황에서, 모든 api별로 각 컨트롤러에서 권한 체크가 들어간다면, 중복코드가 너무 많이 발생한다.
  2. 1과 같은 불편함이 미들웨어가 존재하는 이유라고 생각한다. api 사용 전, 미들웨어에서 처리하여 api 사용 권한을 정해주는 것이 맞다고 생각했다.


코드로 보기

// middleware/auth
const checkToken = async (req, res, next) => {
  if (!req.cookies.accessToken) {
    return res.status(CODE.UNAUTHORIZED).json(form.fail(MSG.UNAUTHORIZED));
  }

  const accessToken = await jwt.verifyAccessToken(req.cookies.accessToken);
  const refreshToken = await jwt.verifyRefeshToken(req.cookies.refreshToken);

  if (accessToken === TOKEN_INVALID || refreshToken === TOKEN_INVALID) {
    return res.status(CODE.UNAUTHORIZED).json(form.fail(MSG.UNAUTHORIZED));
  }
  if (accessToken === TOKEN_EXPIRED) {
    if (refreshToken === TOKEN_EXPIRED) {
      // access 만료 refesh 만료
      return res.status(CODE.UNAUTHORIZED).json(form.fail(MSG.UNAUTHORIZED));
    }
    // access 만료 refesh 유효
    const redisToken = await redis.get(refreshToken.idx);

    if (req.cookies.refreshToken !== redisToken) {
      return res.status(CODE.UNAUTHORIZED).json(form.fail(MSG.TOKEN_INVALID));
    }

    const newAccessToken = await jwt.signAccessToken({ idx: refreshToken.idx, email: refreshToken.email });

    res.cookie('accessToken', newAccessToken);
    req.cookies.accessToken = newAccessToken;

    next();
  } else if (refreshToken === TOKEN_EXPIRED) {
    // access 유효 refesh 만료

    const newRefreshToken = await jwt.signRefreshToken({ idx: accessToken.idx, email: accessToken.email });

    redis.set(accessToken.idx, newRefreshToken);
    res.cookie('refreshToken', newRefreshToken);
    req.cookies.refreshToken = newRefreshToken;

    next();
  } else {
    // access 유효 refesh 유효
    req.cookies.userIdx = accessToken.idx;
    next();
  }
};


// routes/user
router.post('/join', Validator.join, ValidatorError.err, userCtrl.join);
router.post('/login', Validator.login, ValidatorError.err, userCtrl.login);
router.get('/logout', auth.checkToken, userCtrl.logout);

다음과 같이 미들웨어로 auth/checkToken을 작성해놓고,

로그인 된 사용자만 접근 가능한 api는 미들웨어에 auth.checkToken을 넣어두었다.

사용해보니 기대하지 않았던 좋은 점을 발견했는데,

accessTokenjwt로 암호화할때, userIdx를 넣어놨는데, 그렇게 하다보니, auth.checkToken을 거치면, path param, query, body 값으로 userIdx를 받지 않아도, 바로 요청한 유저의 userIdx를 알아낼 수 있었다.

그래서 userIdx 정보가 필요한 api는 payload로 해당 데이터를 별도로 받지 않고, 쿠키를 파싱하여 값을 미들웨어에서 컨트롤러로 보내주어 데이터를 사용할 수 있었다🥳🥳