[React] Refresh Token Rotation 방식이란?

Lim Jeong Hu·2025년 2월 21일
post-thumbnail

씨즌넷 프로젝트에서 내가 맡은 부분 중 VideoUpload에는 서버로 데이터를 보내기 때문에 Post를 사용하였다. 때문에 access token이 필요했는데, 우리 프로젝트는 jwt를 기반으로 refresh token rotation 방식을 사용한다는 이야기를 들었다. 그래서 정확히 어떤 방식인지 궁금해서 찾아보게 되었다.

🔍JWT(Json Web Token)란?

JWT (JSON Web Token) 개념

  • 로그인 시, 서버에서 클라이언트에게 토큰(JWT)을 발급한다.
  • 클라이언트가 API 요청 시, JWT를 함께 보내면 서버에서 인증 처리를 한다.

JWT 토큰 구조

JWT는 헤더(Header), 페이로드(Payload), 서명(Signature) 세 파트로 나누어져 있다.

  • Header: 알고리즘, 타입 정보가 들어 있다.
  • Payload: 전달하려는 사용자 정보 (ID, 역할 등)를 담고 있다.
  • Signature: secret key로 암호화시켜 위변조를 방지하는 서명을 한다

⚠️기존 Refresh Token 방식

JWT 기반 인증에서는 일반적으로 다음과 같은 흐름을 따른다.

기존 방식 (일반적인 Refresh Token 사용)

  1. 사용자가 로그인하면, 서버가 Access Token (짧은 유효기간)Refresh Token (긴 유효기간)을 발급한다.
  2. Access Token이 만료되면, 클라이언트가 Refresh Token을 사용해 새로운 Access Token을 요청한다.
  3. Refresh Token은 서버가 만료될 때까지 재사용 가능하다.

하지만 이 방식에는 치명적인 보안 문제가 있다.

만약 Refresh Token이 탈취된다면?

  • 해커가 Refresh Token을 이용해 계속 새로운 Access Token을 발급받을 수 있다.
  • 유출된 Refresh Token을 서버에서 막을 방법이 없기 때문에, 로그아웃을 해도 공격자는 계속 인증 가능하다.

🔐Refresh Token Rotation 방식

이 문제를 해결하기 위해 Refresh Token Rotation 방식을 사용할 수 있다.

Refresh Token Rotation 방식의 원리

  • Refresh Token을 한 번만 사용할 수 있도록 설계한다.
  • 새로운 Access Token을 요청할 때마다 새로운 Refresh Token을 발급한다.
  • 기존 Refresh Token은 즉시 폐기하고, 탈취된 토큰은 재사용 불가하도록 만든다.

Refresh Token Rotation 흐름🔑

  1. 사용자가 로그인하면, Access Token & Refresh Token을 발급한다.
  2. Access Token이 만료되면, 클라이언트가 Refresh Token을 사용하여 새로운 Access Token 요청한다.
  3. 서버는 새로운 Access Token과 새로운 Refresh Token을 발급한다. (이전 Refresh Token은 폐기)
  4. 이전 Refresh Token이 다시 사용되면 즉시 차단한다.

즉, Refresh Token이 탈취되더라도 공격자는 재사용할 수 없다.

👍Refresh Token Rotation 방식의 장점

그래서 장점을 크게 세 가지로 말하자면

  • Refresh Token이 탈취되어도 공격자가 사용할 수 없고
  • 로그아웃 시, 즉시 Refresh Token을 폐기 가능하고
  • 프론트엔드에서 별도의 토큰 블랙리스트 관리가 필요 없다.

이 방식은 Auth0, Google OAuth 등에서도 많이 사용되는 방식이다.

🔍 프로젝트에서 Refresh Token Rotation 방식이 사용된 부분

씨즌넷 프로젝트는 API 호출을 효율적이고 일관되게 사용하기 위해 axios 인스턴스를 사용하였는데, 이런 부분들에서 Refresh Token Rotation이 사용되었음을 알 수 있다.

크게 요청 인터셉터와 응답 인터셉터로 나눌 수 있는데, 먼저 요청 인터셉터이다.

if (request?.status === 401 && !request._retry) {
    //  아래 코드는 토큰 갱신이 완료되면 모두 새 토큰을 사용해 재시도하도록 함
    if (refreshFlag) {
                return new Promise(function (resolve, reject) {
                    failedQueue.push({ resolve, reject });
                })
                .then((token) => {
                    request.headers["Authorization"] = "Bearer " + token;
                    return SSIZENNET_API(request);
                })
                .catch((err) => {
                    return Promise.reject(err);
                });
            }

    request._retry = true;
    refreshFlag = true;

    // refresh token을 가져와서 새 access token을 요청하는 코드(잠시 생략)
}

먼저 이 부분을 통해 401 에러를 반환할 경우 재시도 플래그를 확인하여 토큰 갱신 과정을 진행함을 보여준다.

중간의 코드는 이미 토큰 갱신이 진행 중일 때, 다른 요청들을 failedQueue에 넣어 새로운 토큰이 발급되면 모두 함께 재시도할 수 있도록 큐에 저장하는 역할을 한다.

다음은 응답 인터셉터이다.

const res = await axios.post(`${data_api}/auth/refresh`, {
    refresh: refresh_token,
});
const accessTokenValue = res.data["acess"];
const refreshTokenValue = res.data["refresh"];
sessionStorage.setItem("access-token", accessTokenValue);
sessionStorage.setItem("refresh-token", refreshTokenValue);
SSIZENNET_API.defaults.headers["Authorization"] = `Bearer ${accessTokenValue}`;
request.headers["Authorization"] = `Bearer ${accessTokenValue}`;
processQueue(null, accessTokenValue);
refreshFlag = false;
return SSIZENNET_API(request);

이 코드는 위에서 생략한 토큰 갱신 및 업데이트 기능을 하는 코드인데, refresh token을 사용하여 새로운 access token 및 refresh token을 받아서 sessionStorage와 기본 헤더를 업데이트한 후, 대기 중인 모든 요청(failedQueue)을 새 토큰으로 재요청하는 로직이다.

💡 결론

나는 accesstoken을 직접 env 파일에 입력하는 방식으로 코딩하여서 하드 코딩을 한다고 생각했는데, axios 인스턴스 속에서 이렇게 복잡한 Refresh Token Rotation 로직이 작동되고 있다는 것을 알고 나니 놀라웠다. 코드 하나하나 뜯어보며 공부하는 것의 중요성을 느꼈고, 더 많은 CS 지식을 익히고 싶다는 생각이 들었다.

profile
종강주세요

1개의 댓글

comment-user-thumbnail
2025년 3월 3일

고생했습니다 코드분석도 잘했고 느낀 점이 너무 맘에드네요 앞으로도 많은 공부를 하고 많은 코드를 보면서 성장하길 기대할게요:)

답글 달기