nodejs - refresh token을 이용해서 access token 만료 이후에도 자동 재발급 해주기

김예지·2021년 8월 6일
1

refresh token을 다루는 방법이 굉장히 많다. 저장하는 방식부터, 불러오는 방식까지 모두 다르며 각각의 장/단점이 존재한다.
이번 프로젝트에서 차용한 방법은 다음과 같다.
1. 디비(Mysql)에 refresh_token 컬럼 추가
2. 로그인시 refresh_token 발급해서 디비에 저장
3. middleware에서 로그인 권한이 필요해서 jwt 인증을 verify할 경우, access token이 만료되었다면 디비에서 refresh token 찾아와서 verify한다.
4. refresh token이 valid하다면 access token을 재발급해주고, invalid하다면 로그인을 새로 하게 한다.

구현은 다음과 같다.

라우터 부분

router.post(
  "/login",
  passport.authenticate("local", {
    session: false,
    failureRedirect: "/auth/fail",
  }),
  async function (req, res) {
    try {
      const user = req.user;
      const token = jwt.sign({ user_id: user.user_id }, "secret", {
        expiresIn: "1200s",
      });
      const refresh_token = jwt.sign({}, "secret", {
        expiresIn: "14d",
      });
      await user.update(
        { refresh_token },
        { where: { user_id: user.user_id } }
      );
      res.status(200).send({ message: "success", token: token });
    } catch (err) {
      res.status(400).send({ message: err + " : login failed" });
    }
  }
);

middleware 부분

  • header에서 Authorization : Bearer token 형태로 넘겨주는 상황
const jwt = require("jsonwebtoken");
const { user } = require("../models");

module.exports = (req, res, next) => {
  const { authorization } = req.headers;

  if (authorization == null) {
    res.status(401).send({
      errorMessage: "로그인이 필요합니다.",
    });
    return;
  }

  const [tokenType, tokenValue] = authorization.split(" ");

  if (tokenType !== "Bearer") {
    res.status(401).send({
      errorMessage: "로그인이 필요합니다.",
    });
    return;
  }

  try {
    const myToken = verifyToken(tokenValue);
    if (myToken == "jwt expired") {
      // access token 만료
      const userInfo = jwt.decode(tokenValue, "secret");
      console.log(userInfo);
      const user_id = userInfo.user_id;
      let refresh_token;
      user.findOne({ where: user_id }).then((u) => {
        refresh_token = u.refresh_token;
        const myRefreshToken = verifyToken(refresh_token);
        if (myRefreshToken == "jwt expired") {
          res.send({ errorMessage: "로그인이 필요합니다." });
        } else {
          const myNewToken = jwt.sign(
            { user_id: u.user_id },
            "secret",
            {
              expiresIn: "1200s",
            }
          );
          res.send({ message: "new token", myNewToken });
        }
      });
    } else {
      const { user_id } = jwt.verify(tokenValue, "secret");
      user.findOne({ where: user_id }).then((u) => {
        res.locals.user = u;
        next();
      });
    }
  } catch (err) {
    res.send({ errorMessage: err + " : 로그인이 필요합니다." });
  }
};

function verifyToken(token) {
  try {
    return jwt.verify(token, "secret");
  } catch (error) {
    return error.message;
  }
}

이렇게 구현해두면 추후에 프론트엔드에서 만료된 토큰을 헤더에 보냈을 때, 새로운 토큰을 재발급해서 response로 넘겨준다. 다음과 같은 형태이다.

profile
새싹

1개의 댓글

comment-user-thumbnail
2023년 10월 17일

참고가되네요ㅎㅎ 감사합니다!

답글 달기