현재 인턴 프로젝트 진행 중 혼자 삽질하고...사수님과 함께 해결한 axios 관련 에러에 대한 기록.
내가 이전 팀프로젝트 시 해당 부분을 다뤄본 적이 없고, 관련 지식을 잘 알고 있지 못한 부분도 있어서 오류 원인의 파악이 어려웠기에 에러 해결 + 지식 채우기 기록으로 남겨보려 한다.
회원가입 후 바로 초기 등록이 이뤄지는 페이지 구현 중, 회원가입 POST API 요청을 하여 토큰(accessToken
)을 받았고 받아온 토큰을 localStorage
에 저장한 상태
회원가입 후 페이지를 벗어나지 않은 상태에서(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>
</>
)
}
초기 등록 API 요청 직전까지 console.log로 토큰을 localStorage
에서 잘 가져오고 있는 것을 확인했으나, 실제 API 요청 시 개발자도구 네트워크 탭을 들어가 확인하니Authorization
header에 토큰이 안담겨 null
로 보내지면서 Bad Request 에러가 발생(사진과 같이)
다만 토큰이 저장된 상태에서 페이지를 벗어났을 때의 다른 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
에 원인이 있었다. (!)
getAccessToken
을 호출하기 전에 axios instancewithToken
이 실행되면서 토큰 값을 받지 못한 것이 문제localStorage
에서 토큰 받아오는 작업이 새로고침이 한 번 수행되었을 때 요청되는 기준이 있는 듯하다.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;
accessToken
을 바로 instanceWithToken이라는 API 요청 인스턴스의 기본 Authorization 헤더에 설정별도로 헤더를 설정할 필요 없이 매번 토큰이 자동으로 포함되도록 하는 방식이나 실패했다...
알고 보니 프로젝트 세팅에 토큰을 갱신해 주는 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을 한 번 받아오는 작업 수행해 해결!
서버로의 요청이나 응답을 가로채어 변형, 처리할 수 있는 기능으로, 요청 인터셉터(request)와 응답 인터셉터(response)가 존재한다.
POST와 같은 Axios 요청이 서버에 보내지기 전에 요청을 가로챈다.
내가 해결한 방법에서와 같이, 모든 요청에 토큰을 추가해야 하는 경우 사용할 수 있다.
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);
}
);