accessToken과 refreshToken의 저장위치

Uhan33·2024년 2월 5일
0

TIL

목록 보기
24/72

저번에 다루었던 accessToken과 refreshToken에 대한 이야기를 한번 더 하려고 한다.
개인과제 코드 리팩토링 중 accessToken과 refrshToken을 사용할 때 접근 방식이 이상했던 것 같아 다시 개념을 정리하고 코드를 다시 짜보았다.

아래는 간단하게 구현한 refreshToken으로 accessToken을 재발급받는 코드이다.

router.post('/refresh', async (req, res, next) => {
  const { refreshToken } = req.body;
  const decodedToken = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET_KEY);

  if (!decodedToken.userId) return res.status(400).json({ message: '토큰 정보가 올바르지 않습니다.' });

  const tokenStorage = await prisma.tokenStorage.findFirst({
    where: {userId: decodedToken.userId}
  });

  if(tokenStorage.refreshToken !== refreshToken)
    return res.status(400).json({message: "발급받은 refreshToken과 일치하지 않습니다."});
  const user = await prisma.users.findFirst({
    where: {
      userId: +decodedToken.userId,
    },
  });

  if (!user) return res.status(400).json({ message: '유저 정보가 없습니다.' });

  const token = jwt.sign({ userId: user.userId }, process.env.ACCESS_TOKEN_SECRET_KEY, { expiresIn: '12h' });

  res.cookie('authorization', `Bearer ${token}`);
  return res.status(200).json({ message: '토큰 재발급 완료!' });
});

req.body로 refreshToken을 받고 토큰 안에 들어있는 userId로 DB에 저장되어있는 refreshToken을 가져와 body의 토큰과 DB에 저장된 토큰이 일치하는지 검사하고,
일치하다면 해당 userId의 정보가 담긴 accessToken을 재발급해준다.


문제발견

위 코드를 보면 알 수 있듯이 refreshToken을 DB에 저장하여 사용하는 방식으로 구현했다.
그러다가.. 문제가 발생했다. AcessToken이 만료되었을 때 refreshToken을 어떻게 가져오지..?

아래는 인증 미들웨어 중 일부 코드이다.

 if(new Date().getTime() >= (decodedToken.exp-300)*1000) {
   const refreshToken = await prisma.tokenStorage.findFirst({
     where: {userId: +userId}
   });
   if(refreshToken) {
     const token = jwt.sign({ userId: refreshToken.userId }, process.env.ACCESS_TOKEN_SECRET_KEY, { expiresIn: '1h' });
     res.cookie('authorization', `Bearer ${token}`);
     return res.status(401).json({message: "토큰이 만료되어 재발급하였습니다. 재시도 바랍니다."});
   }
 }

AccessToken의 만료시간 5분 전에 인증 미들웨어를 거치면
해당 유저가 refreshToken이 있는지 검사하고, 있다면 토큰을 재발급해준다.(1시간)

토큰이 만료되기 전에는 이렇게 AccessToken을 재발급할 수 있다.
그런데 토큰이 만료되면 AccessToken에 접근할 수 없고,
유저에 대한 정보가 쿠키에도 어디에도 남아있지 않게 되어
DB에 저장된 해당 유저의 정보가 들어있는 refreshToken을 가져올 방법이 생각나질 않았다.

어떻게되었든 AccessToken이 만료되면 결국 로그인을 다시 해야하는 상황인 것이다..

문제 발생의 원인

refreshToken을 DB에 넣어두어 소중하게 보관하려 했었다.
refreshToken을 사용하는 이유는 자동로그인과
accessToken의 만료시간을 짧게 하여 탈취당하였을 때 피해를 최소화 하기 위함으로 알고있다.

그래서 refreshToken을 안전하게 보관하고싶었고, 쿠키에 accessToken과 같이 넣어두면 안될 것 같다는 생각에 DB에 저장하는 방식을 택했다.
그러고나니 accessToken이 만료되면 해당 유저의 refreshToken에 접근할 방법이 없어졌고,
결국 다시 로그인을 해야하는 유저 입장에서는 귀찮은 상황이 발생하게 된 것이다.

해결

해결이라고 하기보단 회피에 가깝다.

결국 DB에서만 저장하고 사용하는 방법은 AccessToken 만료 전에 로그인 연장만 할 수 있도록 하고(은행이나 홈택스처럼), 만료되면 로그인을 다시 해야한다.
(이 외에는 방법이 생각나지 않는다.. 찾게 된다면 본문을 수정하겠다.)

다른 방법은 refreshToken도 그냥 accessToken처럼 쿠키에 저장하는 방법이다.
사실 로그인할 때 빼고, 다른 모든 유저 인증이 필요한 작업에는 accessToken만 사용된다.
refreshToken이 사용되는건 accessToken을 재발급 받을 때일 뿐..
그러니 쿠키에 저장해도 괜찮을 것 같다는 생각이 들었다.
(그리고 일반적으로 쿠키에 많이 저장한다고 한다...)

마치며..

결국 속이 뻥 뚫리는 해결방법은 찾지 못했지만
아직 지식이 많이 부족해 아는 만큼만 보이는 것일 수도 있으니
배우고 배우면서 더 좋은 보안 방법을 알게 되지 않을까?
노력해라...

0개의 댓글