Axios | [오류 해결] API 요청 시 Request Authorization에 토큰이 안담기는 현상

dayannne·2024년 3월 10일
0

CS

목록 보기
1/1
post-thumbnail

현재 인턴 프로젝트 진행 중 혼자 삽질하고...사수님과 함께 해결한 axios 관련 에러에 대한 기록.
내가 이전 팀프로젝트 시 해당 부분을 다뤄본 적이 없고, 관련 지식을 잘 알고 있지 못한 부분도 있어서 오류 원인의 파악이 어려웠기에 에러 해결 + 지식 채우기 기록으로 남겨보려 한다.

문제 상황

  1. 회원가입 후 바로 초기 등록이 이뤄지는 페이지 구현 중, 회원가입 POST API 요청을 하여 토큰(accessToken)을 받았고 받아온 토큰을 localStorage에 저장한 상태

  2. 회원가입 후 페이지를 벗어나지 않은 상태에서(URL 변경 없이) 초기 설정 등록을 위한 POST API 요청을 추가로 해야 했음

    즉,
    1) Recoil을 사용하여 페이지 단계별 상태를 관리함
    2) 이 상태값에 따라 컴포넌트를 조건부 렌더링하여 마치 페이지 전환되는 것처럼 구현
    3) 그러나 실제로는 SPA특성상 페이지 이동/새로고침이 없이 단계별 컴포넌트만 전환됨

    /* 코드 예시 */
    
    const pageState = atom({
      key: 'pageState',
      default: 'page1'
    });
    
    function App() {
      // Recoil 상태값 가져오기
      const page = useRecoilValue(pageState);
    
      // 상태값에 따라 컴포넌트 분기 처리
      return (
        <>
          {page === 'page1' && <Page1 />}
          {page === 'page2' && <Page2 />}
          {page === 'page3' && <Page3 />}
        </>
      ) 
    }
    
    // 페이지 전환 버튼 
    function Buttons() {
    
      // Recoil 상태값 설정하기
      const setPage = useSetRecoilState(pageState);
    
      const goToPage1 = () => setPage('page1');
      const goToPage2 = () => setPage('page2');
    
      return (
        <>
          <button onClick={goToPage1}>Go to Page1</button> 
          <button onClick={goToPage2}>Go to Page2</button>
        </>
      )
    }
  3. 초기 등록 API 요청 직전까지 console.log로 토큰을 localStorage에서 잘 가져오고 있는 것을 확인했으나, 실제 API 요청 시 개발자도구 네트워크 탭을 들어가 확인하니Authorization header에 토큰이 안담겨 null로 보내지면서 Bad Request 에러가 발생(사진과 같이)
    다만 토큰이 저장된 상태에서 페이지를 벗어났을 때의 다른 API 요청은 정상 처리되고 있었다...


원인

  • 문제가 발생하는 해당 API 훅
export const usePostInitialSetting = () => {
  const mutate = useMutation({
    mutationFn: async (payload: InitialSettingPayload) => {
      const response = await instanceWithToken.post(`auth/setting`, payload);
      return response.data;
    },
    onSuccess: (data) => {
      console.log(data);
      window.location.replace("/register");
    },
    onError: (error) => {
      console.log(error);
    },
  });

  return mutate;
};

useMutation 훅 내에서 Post API 요청을 보내고 있었고, 다른 훅들과 동일한 형태로 작성되어 있다. 그래서 이 자체의 문제는 없으며,

const getAccessToken = () => {
	return localStorage.getItem("accessToken")
}

export const instanceWithToken = axios.create({
  baseURL,
  headers: {
    /** 서버에서 Bearer 처리를 하므로, 프론트에서는 Bearer를 담지 않음  */
    Accept: "application/json",
    Authorization: `${getAccessToken()}`,
    withCredentials: true,
  },
});

토큰을 가져와 axios headers를 세팅하는 axios instanceWithToken에 원인이 있었다. (!)

  • instanceWithToken에서 getAccessToken을 호출하기 전에 axios instancewithToken이 실행되면서 토큰 값을 받지 못한 것이 문제
  • 다른 페이지 이동 후에는 정상적으로 처리되는 것은 본래 localStorage에서 토큰 받아오는 작업이 새로고침이 한 번 수행되었을 때 요청되는 기준이 있는 듯하다.
  • 그래서 현재 오류 발생한 페이지의 경우 토큰 받아오기 이후 페이지 이동을 하지 않고 Recoil로 컴포넌트를 이동하여 페이지네이션을 진행하고 있는 것이 원인이 되었음

해결 시도

1) (실패 ❌) Axios 인스턴스에 Authorization 헤더 미리 설정하기

export const usePostSignUp = () => {
  const mutate = useMutation({
    mutationFn: async (payload: InitialSettingPayload) => {
      const response = await instanceWithToken.post(`auth/sign-up`, payload);
	  /* 아래 코드 추가*/
      instanceWithToken.defaults.headers.common["Authorization"] = response.data.accessToken;
      return response.data;
    },
    onSuccess: (data) => {
      console.log(data);
      // setPage('page2')와 같이 Recoil pageState 변경
    },
    onError: (error) => {
      console.log(error);
    },
  });

  return mutate;
};
instanceWithToken.defaults.headers.common["Authorization"] = response.data.accessToken;
  1. 회원가입 API 요청을 보내서 응답을 받아온 다음,
  2. 회원가입 API 요청의 응답 데이터에서 받아온 accessToken 을 바로 instanceWithToken이라는 API 요청 인스턴스의 기본 Authorization 헤더에 설정

별도로 헤더를 설정할 필요 없이 매번 토큰이 자동으로 포함되도록 하는 방식이나 실패했다...

2) (해결 ✅) axios.instance.interceptors.request.use

알고 보니 프로젝트 세팅에 토큰을 갱신해 주는 response 인터셉터는 존재하나 request 인터셉터 설정이 누락된 상태였다.

instanceWithToken.interceptors.request.use(
  (config) => {
    const accessToken = getAccessToken();
    console.log(accessToken);
    if (accessToken) {
      config.headers["Authorization"] = `${accessToken}`;
    }
    return config;
  },
  (error) => {
    console.log(error);
    Promise.reject(error);
  }
);

axios.instance.interceptor.use로 request요청 보내기 전 accessToken을 한 번 받아오는 작업 수행해 해결!


💡axios interceptors란?

서버로의 요청이나 응답을 가로채어 변형, 처리할 수 있는 기능으로, 요청 인터셉터(request)와 응답 인터셉터(response)가 존재한다.

요청 인터셉터

POST와 같은 Axios 요청이 서버에 보내지기 전에 요청을 가로챈다.
내가 해결한 방법에서와 같이, 모든 요청에 토큰을 추가해야 하는 경우 사용할 수 있다.

  • 모든 Request마다 Authorization header에 토큰 주입하기
    axios.interceptors.request.use(
      config => {
        config.headers.common['Authorization'] = 'Bearer ' + token;
        return config;
      }  
    )

응답 인터셉터

반대로 응답 인터셉터는 서버 응답을 받으면 동작한다.
예를들어, refreshToken으로 새 accessToken을 받아로는 기능을 추가할 수 있다.

  • refreshToken으로 accessToken 갱신하기

    axios.interceptors.response.use(
      (response) => response,
      async (error) => {
        // 토큰 만료 에러 코드인 경우
        if (error.response && error.response.status === 401) {
          // 리프레시 토큰이 있다면, 리프레시 토큰으로 액세스 토큰 재발급 받기
          if (refreshToken) {
            const { accessToken } = await axios.post('/token', {
              refreshToken
            });
            // 재발급 받은 액세스 토큰 저장, 요청 헤더에 새 토큰 적용
            setAccessToken(accessToken);
            error.config.headers['Authorization'] = 'Bearer ' + accessToken;  
    
            // 요청 재시도 
            return axios(error.config);
    
          } else {
            // 리프레시 토큰 만료
          }
        }
        return Promise.reject(error);
      }
    );
    

참고

profile
☁️

0개의 댓글