티스토리 뷰

이전 내용에 이어서 axiosInstance코딩한 내용을 작성해보고자 한다.

 

fetch보다 사용성도 좋고 error 핸들링이 좋은 axios 알랍유야..

 

리펙토링 되었습니다. 리펙토링은 간단한 코드분리 정도이므로 아래 블로그를 참고해주세요.

https://anywhereim.tistory.com/112

 

💡먼저 axiosInstance를 작성하게된 이유를 설명해보고자 한다.

1. jwt 토큰을 발급받고 나면 사용자가 요청을 보낼때마다 header에 발급받았던 accessToken을 보내주어야 한다.
2. accessToken은 유효기간이 짧기때문에 통신이 일어났을때 accessToken이 만료되면 refreshToken을 이용해 빨      리 재발급을 받아 사용자의 요청이 이루어질 수 있도록 해주어야 한다.
3. refreshToken이 만료되는 경우에는 다시 로그인 할 수있도록 해야한다.

 

결국 통신이 일어날때마다 인가인증이 필요하기 때문에 axiosInstance의 필요성을 느꼈다. 

 

자그럼..! 

작성하게 된 이유에 따라서 내가작성한 axiosInstance 설명을 해보도록 하겠다👻

 

이부분은 간략하게 설명만 하고 넘어가겠.. 

>> src/app/api/axiosInstance.ts

const axiosInstance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_BASE_URL,
  headers: {
    "Content-Type": "application/json",
  },
  responseType: "json",
});
✅baseURL 
 - 요청을 보낼때 기본 주소 값을 넣어주면 된다. 
✅headers
 - 데이터 형식 명시 
     1. 클라이언트가 보내는 요청 본문의 타입을 서버에 알려주는 역할
✅responseType
 - 서버로부터 받응 응답 데이터 타입 지정
     1. 서버로부터 받응 응답을 해당 responseType에 맞게 자동으로 변환

 

1. header에 accessToken 넣어주기

// 요청 인터셉터 설정
axiosInstance.interceptors.request.use(
  (config) => {
    const token = sessionStorage.getItem("token");
    if (token) {
      config.headers["Authorization"] = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

-  세션스토리지에 넣어둔 토큰을 꺼내고 

- 토큰이 있는경우 config.headers["Authorization"] = `Bearer ${token}` 다음과 같이 작성하고 config를 return해주면 된다.

 

✅ interceptors
요청이 서버로 전송되기 전이나 응답을 받은 후에 특정 코드를 실행할 수있게 해주는 기능.
즉, 모든 요청이나 응답을 중간에 가로채어 추가 로직을 실행할 수있게 해준다.

앞서 필요한 이유를 설명했던 것을 보면 요청이나 응답을 받을 때 인가인증을 위해서 해주어야 하는 것들이 있다. 
●요청 시 header에 accessToken 담아서 보내주기
●요청 시 accessToken이 만료되었다면 refreshToken을 이용해 재발급 후 재요청 
● refreshToken을 이용해 accessToken을 재발급 요청하였으나 refreshToken이 만료시 재로그인    

위 내용은 모든 요청이나 응답을 중간에 가로채어 로직을 실행해주면 사용자는 알수 없겠지요..!!

 

✅ axiosInstance.interceptors.request.use
아 메소드는 요청을 보내기 전에 실행되는 코드를 설정하는데, 두 개의 함수를 인자로 받는다.

● 첫 번째 인자: 요청이 성공적으로 처리될 때 실행 
● 두 번째 인자: 요청이 실패할 때 실행
✅ config
config 객체는 Axios 요청 구성 설정을 담고있다. 
이 객체를 수정해서 요청의 헤더, URL, timeout 등을 조정할 수 있다.

 

 const token = sessionStorage.getItem("token");
나의 경우 세션스토리지에 토큰값을 넣어두었기 때문에 다음과 같이 토큰을 꺼내어 사용


if(token) { config.headers["Authorization"] = `Bearer ${token}`; }
● 헤더에 Authorization 필드로 설정
● Bearer 는 토큰이 OAuth 2.0 type 이라는 것을 나타내는데 이는 백엔드에서 요청하는 이름으로 
사실 백앤드가 아래 이름처럼 요청해서.. 헤더 인증에 쓰이는 규약같은 key 값이라서 큰 변동은 없을 것이다..!!

 

✅ return config;
수정된 config 객체를 반환해서 Axios 요청이 이설정을 사용하도록 할 수 있다.
즉 모든 요청에 인증토큰이 포함되어 서버로 전송된다.

 


✅ Promise.reject(error)

Promise.reject는 프로미스(promise)가 실패하였음을 감지해 오류 처리

 

⬇️ 요청중 401에러가 감지되었을 때 코드 실행 흐름 단계 (전체적인 내용을 쉽게 이해할 수 있음)

더보기

1. 사용자 요청 발송:

- 사용자가 axiosInstance를 사용하여 서버에 요청.

  예를 들어, 사용자 정보를 조회하는 API 호출이 있을 수 있습니다.

 

2​. 요청 인터셉터 실행:

- 요청 인터셉터가 요청을 가로채고, config 객체에 액세스 토큰이 존재하면 Authorization 헤더를 설정.

  이후 수정된 config 객체를 반환하여 서버로 요청.

 

3. 서버 응답:

- 서버로부터 응답을 받음.

  만약 액세스 토큰이 만료되었거나 유효하지 않다면, 서버는 401 상태 코드로 응답을 줌.

 

4​. 응답 인터셉터 오류 처리:

- 응답 인터셉터가 서버 응답을 가로채 401에러가 응답으로 왔다면, 아래의 처리가 실행.

 

5. 토큰 재발급 시도:

- getNewAccessToken 함수가 호출. 이 함수는 리프레시 토큰을 사용해 새 액세스 토큰을 요청.

 

6. 새 토큰 처리:

- 새 액세스 토큰이 성공적으로 발급되면,

  세션 스토리지에서 기존의 토큰과 사용자 정보를 제거한 후, 새 토큰을 저장.

 

7. 원래 요청 재시도:

- 새로 발급받은 액세스 토큰으로 원래의 요청을 수정해 재요청.

  originalRequest.headers["Authorization"] = Bearer ${newAccessToken}; 라인에서 이 처리가 수행.

 

8. 재요청 응답 처리:

- 수정된 요청이 서버로부터 정상적인 응답을 받으면, 이 응답을 원래 요청을 한 코드로 반환.

 

9. 토큰 재발급 실패 처리:

- 새 토큰을 받는 데 실패하면, doneRefreshToken 함수가 호출되어 사용자에게 세션 정보가 제거되었음을 알리고,      로그인 페이지로 리다이렉트.

 

10. 최종 오류 처리:

- 모든 재시도 후에도 문제가 해결되지 않으면,

   Promise.reject(error)를 통해 최종적으로 에러를 호출자에게 전달합니다.

  호출자는 이 에러를 .catch 블록을 통해 처리할 수 있음

  쉽게 생각했을 때 호출자는 해당 axiostInstance를 사용하는 api 요청 코드가 된다고 생각하면 된다!

 

위에 상세하게 적어두어 아래 코드를 쉽게 해석할 수 있을 것이라 생각하므로 아래 내용은 상세 설명 패쑤 

혹시나 제가 작성한 글을 참고하시는 분이 계시다면 댓글달아주시면 확인하겠습니다..



2. accessToken 만료 시 refreshToken을 이용한 재발급

//accessToken 만료 시 refreshToken을 이용한 재발급

const getNewAccessToken = async () => {
  try {
    const response = await axios.post(
      `${requests.getNewAccessToken}`,
      {},
      {
        withCredentials: true, // 쿠키에 저장된 refresh-token을 사용
      }
    );
    const newAccessToken = response.data.accessToken;
    sessionStorage.removeItem("token");
    sessionStorage.removeItem("user");

    await decodeTokenOnServer(newAccessToken);

    sessionStorage.setItem("token", newAccessToken);
    return newAccessToken;
  } catch (error) {
    console.error("Failed to refresh access token", error);
    return null;
  }
};

 

 

3. access, refresh Token 만료 시 재발급 또는 로그인페이지로 리다이렉트

// 응답 인터셉터 설정
axiosInstance.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;

    if (error.response.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

      const newAccessToken = await getNewAccessToken();

      if (newAccessToken) {
        // 원래 요청의 헤더를 새로운 토큰으로 업데이트
        originalRequest.headers["Authorization"] = `Bearer ${newAccessToken}`;
        return axiosInstance(originalRequest);
      } else {
        // 새로운 토큰 발급 실패 시
        // 사용자에게 다시 로그인할 수 있도록!
        doneRefreshToken();
        sessionStorage.removeItem("token");
        sessionStorage.removeItem("user");
        window.location.href = "/users/login";
        return Promise.reject(error);
      }
    }

    return Promise.reject(error);
  }
);