본문 바로가기

project

Final Project: 4주차 (express-middle-ware)

백엔드 포지션을 맡은 나는 프론트엔드에 비해 비교적 작업이 빨리 끝나게 되어 남은 기간동안 코드 퀄리티를 좀 더 살려보기 위해 이것저것 리팩토링을 시도해보았습니다.

 

그 중 jwt 토큰기반의 인증방식을 따로 미들웨어로 적용시켰던 부분에 대해 글을 남겨보려 합니다.

 

원래의 코드에서는 각 api중 인증정보가 필요한 부분들의 가장 위쪽에 직접 토큰검증로직을 일일이 넣어줬었습니다.

코드로 살펴보면,

 

// 토큰이 유효한지 검사하는 jwt.verify를 promise화 하여 따로 함수로 빼낸 부분
const verifyToken = (token, secretKey) => {
  return new Promise((resolve, reject) => {
    jwt.verify(token, secretKey, (err, decoded) => {
      if (err) reject(err);
      else resolve(decoded);
    });
  });
};

const service = new adminService();

export default class AdminController {
...
  // router를 통해 controller로 req가 들어오면,
  async createCouponController(req: Request, res: Response): Promise<void> {
    try {
      const token = req.headers.authorization;
      await verifyToken(token, process.env.JWT_SECRET_KEY);
      await service.createCouponService(req.body);
      res.status(201).end();
    } catch (err) {
      res.status(401).end();
    }
  }
  ...
}

 

createCoupon이라고 이름지은 어떤 api의 엔드포인트로 요청이 들어오면 토큰을 확인하여 그 토큰이 유효할 경우 다음 로직들을 진행할 수 있도록 작성해 놓았습니다.

이 부분을 살펴보다가  catch구문이 실행되었을 때 두개의 await 구문중에 어떤곳에서 오류가 발생한 것인지 알 수가 없다는 것을 발견하게 되었습니다.

 

이것을 개선하기 위해 가장먼저 생각난것은 '두개의 try-catch 구문을 사용해야 할까?' 였고 좋은 방법은 아닐거라는 생각이 들었습니다.

다음으로 든 생각은 '인증이 필요한 api들은 controller까지 오기 전에 인증정보를 확인할 수 있다면 좋을것 같다!' 였습니다.

 

그 결과로 전부터 대략적으로만 알고있었던 express의 미들웨어를 사용해보기로 했습니다.

이 프로젝트의 디렉토리구조를 살펴보면

app.ts  =>  routes  =>  controller  =>  service  =>  model

의 흐름으로 진행이 될 수 있도록 해놓았는데

실제로 controller를 호출하는 routes 파일에서 토큰검증에 필요한 미들웨어를 작성하여 controller 호출 전에 적용시키면 될것 같다는 생각이 들었습니다.

 

그 결과로 만들어진 routes는 아래와 같습니다.

 

import dotenv from "dotenv";
import express from "express";
import AdminController from "../controller/admin";
import jwt from "jsonwebtoken";
import { ERROR_MESSAGE } from "../common/ErrorMessages";

dotenv.config();
const controller = new AdminController();
const router = express.Router({ strict: true });

const jwtCheck = (req, res, next) => {
  const token = req.headers.authorization;
  jwt.verify(token, process.env.JWT_ADMIN_SECRET_KEY, (err, decoded) => {
    if (err) {
      // 토큰이 유효하지 않으면 에러메세지와 함께 403 응답을 보낸다. 다음콜백함수로 넘어가지 않는다.
      res.status(403).json(err ? err : { message: ERROR_MESSAGE.WRONG_TOKEN });
    } else {
      // req.tokenData에 token해석 정보를 저장하여 다음 콜백함수로 넘긴다.
      req.tokenData = decoded;
      next();
    }
  });
};

...
// 요청이 들어오면 jwtCheck함수가 먼저 실행되어 토큰을 검증한후
// 유효할때만 controller함수가 실행된다.
router.post("/coupon", jwtCheck, controller.createCouponController);

...

 

이렇게 routes단계에서 토큰의 유효성을 검사해주게 되니 controller파일에서의 에러핸들링도 정확하게 해줄 수 있게 되었습니다.

controller

 

...
async createCouponController(req: Request, res: Response): Promise<void> {
    try {
      await service.createCouponService(req.body);
      res.status(201).end();
    } catch (err) {
      res.status(409).send(err.message);
    }
  }
  ...

 

만약 controller에서 토큰에 담겨있는 정보가 필요하다면, routes에서 담아준 대로 req.tokenData로 해당 정보들을 받아올 수 있습니다.