티스토리 뷰

🚀 Access Token과 Refresh Token ?

Access Token는 사용자를 인증하는 토큰으로 유효 기간이 정해져 있어, 해당 유효기간이 끝나게 되면 인증 기간이 만료되었다는 뜻으로 더이상 토큰을 사용할 수 없습니다. 가끔 은행 어플을 사용하다보면 세션이 만료되었다면서 다시 로그인을 하라는 모달창이 뜨는 것을 본 적 있으실 겁니다. 유효기간을 길게 하려면 충분히 길게 할 수는 있지만, 해당 토큰이 탈취되었을 경우에는 얼마든지 악용할 수 있으므로 적당한 유효기간을 정해두는 것입니다.

하지만 보안상의 이유로 유효 기간을 짧게 해두면 당연히 사용자 입장에서는 불편할 수 밖에 없습니다. 따라서 이때 Refresh Token이라는 것으로 Access Token의 유효 기간은 짧고, 자주 재발급 하도록 만들어 보안을 강화하면서도 사용자에게 잦은 로그아웃 경험을 주지 않도록 하는 것입니다.

 

결론적으로는, Access Tokne은 사용자 인증을 위해 사용되는 토큰이라면, Refresh Token은 기존에 사용자가 가지고 있던 Access Token이 만료되었을 때 재발급 받기 위해 사용되는 토큰입니다.

Refresh Token에도 유효 기간⏰이 있다

Refresh Token은 Access Token 대비 긴 유효 기간을 갖습니다. Refresh Tokenㅇ르 사용하는 상황에서는 일반적으로 Access Token의 유효기간은 30분 이내, Refresh Token의 유효 기간은 2주 정도로 설정한다고 합니다. 당연히 서비스 성격에 따라 적절한 유효기간은 충분히 달라질 수 있습니다.

만료된 Access Token을 갱신✨하는 과정

방법1. Refresh Token을 쿠키로 관리하고 있다면

  1. 요청 시 헤더에 withCredentails: true를 해주게 되면
  2. 백엔드와 프론트엔드가 쿠키값을 공유할 수 있어서 따로 Refresh Token을 헤더에 넣고 하지 않아도 된다
  3. 요청에 대한 응답을 매번 가로챌 것이다
  4. 만약 만료된 Access Token으로 요청을 보냈었다면 그 응답값으로는 에러가 올 것이다
  5. 에러가 왔다면 가로채서 실행할 로직이 존재하게 된다(따라서 사용자는 에러가 났는 줄도 모른다)

🚀 구현해보자

0. 커스텀 인스턴스를 만든다

export const Axios = axios.create({
  baseURL: process.env.REACT_APP_BACKEND_URL,
  withCredentials: true, // 프론트 웹 스토리지의 쿠키를 백엔드와 공유 가능
})

1. 요청 가로채기

즉, 클라이언트가 요청을 보내면 서버에 도착하기 전에 가로채서 실행될 로직을 작성합니다.

Axios.interceptors.request.use(
  // 에러가 없다면
  (config) => {
    const access_token = localStorage.getItem('access_token');
    if(access_token) {
       config.headers.Authorization = `Bearer ${access_token}`;
       return config;
    }
    return config;
  },
  
  // 에러가 생겼다면
  (error) => {
    return Promise.reject(error);
  }
)

2. 응답 가로채기

방금 요청을 가로채서 acces token이 있다면 토큰을 헤더에 실어서 보냈었습니다. 그런데 해당 토큰이 만료되었거나 잘못된 토큰일 경우 서버는 클라이언트에게 에러를 보내올 것입니다. 이때 refresh 토큰이 없다면 로그인을 다시 하라고 모달창을 띄우곤 할 것입니다. 그런데 우리는 이 응답을 가로채서 즉, 클라이언트에게 에러가 닿기 전에 가로채서 에러에 맞게 핸들링을 해줄 것입니다.

Axios.interceptors.response.use(
  // 에러가 없다면
  (res) => {
    return res;
  },
  
  // 에러가 생기면
  async (err) => {
    const auth = useAuth(); // 로그인과 로그아웃 로직을 모아둔 전역 Context
    
    if(err.response.status === 401) { // 401: 세션 만료
      // 세션이 만료되었다는 에러가 오면 우선 로그아웃을 해서
      // 내부에 존재하는 access token을 우선 모두 없애고 지운다
      await AuthApi.logout();
      auth.logout();
    }
    
    const originalRequest = err.config; // 에러로 온 객체를 상수에 담아놓는다
    
    if(err.response.status === 403 && !originalRequest._retry) {
      // 403 에러이고 _retry(재요청 보낸 적이 있는지)가 false이면
      orginalRequest._retry = true;
      
      // refresh 토큰으로 access token을 재발급 받는 api에 post 요청
      const res = await Axios.post('/user/jwt');
      if(res.status === 200) { // access token 재발급에 성공했다면
        const token = res.data;
        TokenService.setToken(token);
        Axios.defaults.headers.common['Authorization'] = `Bearer ${token}` // 모든 요청에 적용될 config
        return Axios(originalRequest); // 새로운 토큰을 가지고 재요청 보내는 것
      }
    }
    return Promise.reject(err);
  }
)

우선 에러 없이 응답이 왔다면 그대로 return 합니다.

만약 에러가 응답값으로 왔다면, 그 이후로는 에러 status에 따라 핸들링하려고 했습니다.

제가 지금 요청 보낸 백엔드에서는 access token이 만료되었는데 refresh token이 없다면 401에러를, access token이 만료되었는데 refresh token이 있다면 403 에러를 보내주고 있어 위와 같이 코드를 작성했습니다.

 

일단 access token이 만료되었는데 refresh token이 없다면 즉, 401 에러가 왔다면

access token을 재발급 받을 수 없기 때문에 로그아웃 로직이 실행될 수 있도록 처리했습니다.

추가로 사용자에게 로그인 유지 시간이 만료되었다며 다시 로그인 후 이용해 달라는 안내를 하면 더욱 사용자 경험 측면에서 좋을 것 같습니다.

 

또 다른 경우로 access token이 만료되었고 refresh token이 있다면 즉, 403 에러가 왔다면

제가 지금 사용하고 있는 백엔드 로직에는 refresh token을 쿠키로 클라이언트에게 보내주고, 이 쿠키는 axios 커스텀 인스턴스를 만들 때 withCredentials: true로 하여 클라이언트와 서버가 서로 공유하고 있는 상태입니다. 즉, 특별히 refresh token을 어딘가에 담아서 보낼 필요가 없다는 뜻과 같습니다.

 

아무튼 403 에러가 오면, 에러로 온 객체(originalRequest)를 통해

지금 이 에러로 온 요청 자체가 재요청이 되어 갔던 요청(._retry)인지 확인합니다. 만약 재요청으로 전송된 요청이 아닐 때만 재요청을 시도합니다. 해당 조건이 없다면 무한 요청을 보낼 것입니다.

재요청이 아니라면, refresh token을 통해 access token을 재발급 받는 api에 요청을 보냅니다. 해당 요청에 대해 성공적으로 응답이 왔다면 access token을 다시 세팅하는 로직을 작성해주면 됩니다. 

728x90
LIST
250x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함