[React] Axios 커스텀으로 jwt토큰 재발급

이한형·2021년 12월 18일
6

Axios

Axios는 node.js와 브라우저를 위한 Promise 기반 HTTP 클라이언트 입니다.
axios를 통하여 api 요청을 보내서 응답을 받습니다.

jwt 토큰

로그인을 통한 인증을 할때 jwt 토큰을 많이 사용을합니다.
그런데 토큰을 하나로만 인증을 할때 해당 토큰이 탈취가 된다면 보안문제에 이슈가 발생합니다.
그래서 해당 토큰의 유효기간을 짧게 설정을 한다면 유저 입장에서는 토큰이 만료가 됐을때 마다 다시 로그인을 해야하는 불편함을 겪을 수 있습니다.
그래서 로그인을 할 때, access 토큰과 refresh 토큰을 함께 발급을 해주는 방식이 있습니다. refresh 토큰을 유효기간을 길게 가져가고 access 토큰이 만료됐을때 새로 발급을 해주는 키가 됩니다.
즉 access 토큰은 짧은 유효기간을 가져가고 해당 토큰이 만료가 되면 refresh 토큰을 통하여 access 토큰을 재발급하여 사용을 하는 방식으로 보안적 이슈는 어느정도 해결이 됩니다.

axios 커스터마이징

refresh 토큰과 access 토큰을 사용한다고 할 때 클라이언트 측에서는 인증이 필요한 요청을 보낼때 매번 header 에 refresh 토큰과 access 토큰을 추가하여 요청을 보내야합니다.
또한 access 토큰을 만료가 됐을 경우 access 토큰을 재발급 받게끔 요청을 해야합니다.
이러한 작업들은 매 요청마다 추가할려고 하면 코드의 분량이 엄청나게 증가하고 복잡해해질겁니다. 이러한 작업을 axios 커스텀을 통하여 바로 해결을 할 수 있습니다.

import axios from 'axios';

const client = axios.create({
    baseURL: 'http://localhost:4000/'
})

client.interceptors.request.use(
    function (config) {
        const user = localStorage.getItem('user');
        if (!user) {
            config.headers["accessToken"] = null;
            config.headers["refreshToken"] = null;
            return config
        }
        const { accessToken, refreshToken } = JSON.parse(user)
        config.headers["accessToken"] = accessToken;
        config.headers["refreshToken"] = refreshToken;
        return config
    }
)

client.interceptors.response.use(
    function (response) {
        return response
    },
    async function (error) {
      if (error.response && error.response.status === 403) {
          try {
              const originalRequest = error.config;
              const data = await client.get('auth/refreshtoken')
              if (data) {
                  const {accessToken, refreshToken} = data.data
                  localStorage.removeItem('user')
                  localStorage.setItem('user', JSON.stringify(data.data, ['accessToken', 'refreshToken']))
                  originalRequest.headers['accessToken'] = accessToken;
                  originalRequest.headers['refreshToken'] = refreshToken;
                  return await client.request(originalRequest);
                  }
          } catch (error){
              localStorage.removeItem('user');
              console.log(error);
          }
          return Promise.reject(error)
      }
      return Promise.reject(error)
    }
)

export default client;

전체적인 소스코드는 위와 같습니다.
한 부분씩 살펴보면 다음과 같이 이루어져있습니다.

const client = axios.create({
    baseURL: 'http://localhost:4000/'
})

요청을 보내는 baseURL을 http://localhost:4000/ 으로 설정을 합니다.

client.interceptors.request.use(
    function (config) {
        const user = localStorage.getItem('user');
        if (!user) {
            config.headers["accessToken"] = null;
            config.headers["refreshToken"] = null;
            return config
        }
        const { accessToken, refreshToken } = JSON.parse(user)
        config.headers["accessToken"] = accessToken;
        config.headers["refreshToken"] = refreshToken;
        return config
    }
)

reqeust를 보낼때 localStorage에 token 정보가 있다면 헤더에 토큰 정보를 저장하고 없다면 null로 처리를 합니다.

client.interceptors.response.use(
    function (response) {
        return response
    },
    async function (error) {
      if (error.response && error.response.status === 403) {
          try {
              const originalRequest = error.config;
              const data = await client.get('auth/refreshtoken')
              if (data) {
                  const {accessToken, refreshToken} = data.data
                  localStorage.removeItem('user')
                  localStorage.setItem('user', JSON.stringify(data.data, ['accessToken', 'refreshToken']))
                  originalRequest.headers['accessToken'] = accessToken;
                  originalRequest.headers['refreshToken'] = refreshToken;
                  return await client.request(originalRequest);
                  }
          } catch (error){
              localStorage.removeItem('user');
              console.log(error);
          }
          return Promise.reject(error)
      }
      return Promise.reject(error)
    }
)

response를 받았을 때, error가 발생했고 해당 error의 status가 403이라면 기존의 originalRequest를 auth/refreshtoken 으로 전달해 토큰을 재발급 받습니다.
여기서 403 이외의 오류가 들어온다면 토큰 재발급에 실패한것으로 처리를 합니다.
재발급 받은 토큰은 다시 로컬스토리지에 저장을 하고 헤더 부분에서 토큰 정보를 변경하고 다시 originalRequest를 보냅니다.

profile
풀스택 개발자를 지향하는 개발자

6개의 댓글

comment-user-thumbnail
2021년 12월 22일

안녕하세요.. 저도 비슷한 로직으로 개발중인데요. 궁금한게 있습니다. 혹시 포스팅 하신데로
return await client.request(originalRequest); 토큰이 만료 후에 요청을 재 요청 보내는것 아닌가요? 그런데 이 요청은 저도 다시 잘 가더라구요.. 문제는 이게 만약 게시판의 페이지 이동이었거나 상세 보기 였다면 화면단에선 갱신이 안되더라구요..혹시 이런경우는 어떻게 처리하셨나요? 그리고 post,put 요청과 같이 작성 혹은 수정 요청이었을때도 그 행위를 다시 하도록 하시는건가요??

2개의 답글