-
Notifications
You must be signed in to change notification settings - Fork 2
[FE] axios interceptor로 로그인 필요한 api 들 개선하기
SeongYeon edited this page Nov 24, 2024
·
1 revision
현재 우리의 서비스는 로그인을 하지 않고 이용이 가능한 api와 로그인을 한 뒤 access_token을 통해 접근이 가능한 api 들이 존재한다.
로그인을 통해 접근이 가능한 api는 access_token이 만료될 시 401 에러가 발생하며 refresh_token을 통해 access_token을 다시 받아와야 재인증이 가능하다.
위의 access_token을 재발급 받는 로직을 구현하기 위해 axios interceptor로 구현을 해보았다
export const instance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 2000,
});
export async function myAccount(token:string): Promise<Account> {
try {
const response = await instance .get(`/account/myaccount`,{
headers:{
Authorization:`Bearer ${token}`
}
});
return response.data;
} catch (error) {
// axios 에러인지 체크
if (axios.isAxiosError(error)) {
if (error.response?.status === 401) {
try {
// refresh token으로 새 access token 발급 요청
const refreshResponse = await instance.post<{access_token: string}>(
'/auth/refresh',
{},
{ withCredentials: true }
);
// 새 토큰 저장
const new_access_token = refreshResponse.data.access_token;
localStorage.setItem('access_token', new_access_token);
// 원래 요청 재시도
const retryResponse = await authInstance.get('/account/myaccount', {
headers: {
Authorization: `Bearer ${new_access_token}`
}
});
return retryResponse.data;
} catch (refreshError) {
// refresh token도 만료된 경우
localStorage.removeItem('access_token');
window.location.href = '/'; // 로그인 페이지로 리다이렉트
throw new Error('세션이 만료되었습니다. 다시 로그인해주세요.');
}
}
}
throw error; // 401 이외의 에러는 그대로 throw
}
}
- axios interceptor를 사용하기 전 우리의 코드는 하나의 api 인스턴스를 사용하고 있었다.
- axios 에러 상태 코드를 확인하여 401 에러가 발생하면 토큰의 재요청 로직을 보내는 어렵지 않은 로직임을 확인할 수 있다.
- 하지만 이런 방식은 인증이 필요한 각 API 함수마다 토큰 갱신 로직을 중복 작성해야 한다는 단점이 존재한다
- 실제로 우리의 서비스에서 인증이 필요한 API 들은
myAccount
외 엄청나게 많이 존재하기에 위의 문제를 개선해야했다.
axios 인터셉터는 http 요청이나 응답을 보내기 전/후에 가로채서 처리할 수 있게 해주는 기능이다
axios.interceptors.request.use(
// 요청 보내기 전 실행되는 함수
(config) => {
// 요청 성공 처리
return config;
},
// 요청 에러 시 실행되는 함수
(error) => {
// 요청 에러 처리
return Promise.reject(error);
}
);
- request 인터셉터는 api 요청을 보내기 전 api의 설정을 추가할 수 있다.
// 2. Response 인터셉터
axios.interceptors.response.use(
// 응답을 받은 후 실행되는 함수
(response) => {
// 응답 성공 처리
return response;
},
// 응답 에러 시 실행되는 함수
(error) => {
// 응답 에러 처리
return Promise.reject(error);
}
);
- response 인터셉터는 api 요청을 보낸 후 응답을 받은 뒤 로직을 추가할 수 있다.
authInstance.interceptors.request.use(
(config) => {
const access_token = localStorage.getItem('access_token');
if (access_token) {
config.headers.Authorization = `Bearer ${access_token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
},
);
- 기본적으로 우리의 인증 로직은 헤더에 access_token 을 담아서 보내주기에 위와 같이 request 인터셉터에는 로컬 스토리지에 보관하고 있는 access_token을 가져와 헤더에 담아준다
- 위 로직을 request 인터셉터로 처리하면 header 설정을 해주는 코드의 중복을 줄일 수 있다
export const authInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 2000,
withCredentials: true,
});
authInstance.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
try {
const response: AxiosResponse<Login> = await instance.post(
'/auth/refresh',
{
refreshToken: getCookie('refresh_token'),
},
{ withCredentials: true },
);
const new_access_token = response.data.access_token;
const new_refresh_token = response.data.refresh_token;
localStorage.setItem('access_token', new_access_token);
setCookie('refresh_token', new_refresh_token, {
path: '/',
httpOnly: true,
});
originalRequest.headers.Authorization = `Bearer ${new_access_token}`;
return authInstance(originalRequest);
} catch (error) {
localStorage.removeItem('access_token');
removeCookie('refresh_token');
window.location.href = '/';
return Promise.reject(error);
}
}
return Promise.reject(error);
},
);
- response 인터셉터에서 401(인증만료) 에러가 발생했을 때 토큰 갱신 로직이다.
- 401 에러가 발생하면 기존의 refres_token을 활용해 새로운 토큰을 받아온다.
- 만약 access_token을 재발급 받는 로직에 에러가 발생한다면 refresh_token 이 만료됐다는 뜻으로 쿠키를 제거하고 사용자에게 재로그인을 요청해야 한다
export async function myAccount(): Promise<Account> {
const response = await authInstance.get(`/account/myaccount`);
return response.data;
}
- authInstance 만 사용하면 로그인 인증에 필요한 로직을 interceptor 가 알아서 처리해주기에 위와 같이 코드가 간단해진 것을 볼 수 있다.
- [FE] TailwindCSS @apply
- [FE] 캐러셀 구현
- [FE] 사이드 바 상태관리 도전기
- [FE] axios interceptor로 로그인 필요한 api 개선하기
- [FE] Tanstack Query API 최적화 도전기
- [FE] Tanstack Query로 구현하는 무한 스크롤 차트 도전기
- [FE] 차트 무한 스크롤링 최적화 도전기
- [FE] 차트 실시간 등락 구현 도전기
- [FE] 검색 구현 및 검색 API 호출 최적화 도전기
- [FE] 고차 컴포넌트를 활용한 인증 접근 제어
- [FE] 코드 스플릿팅으로 최적화 도전기
- [BE] Server 생성
- [BE] CI/CD
- [BE] GitAction 학습 정리
- [BE] ssh터널링으로 db연결
- [BE] 배포환경에서 DB 연결 및 테스트 완료
- [BE] https 적용
- [BE] upbit api 연결 및 SSE api
- [BE] SSE 구현
- [BE] SSE 에러
- [BE] redis 설치 및 연동
- [BE] 트랜잭션 락 구현과 최적화
- [BE] Oauth CORS
- [BE] QueryRunner 사용 시 발생한 문제점과 해결방안
- [BE] Git Action 학습 정리
- [BE] NestJS 학습 정리
- [BE] 로그인 기능 및 리프레시토큰
- [BE] 비회원 체험 기능
- [BE] Nginx 학습 정리
- [BE] Mixed Content와 HTTPS 보안 구현하기
- [BE] 매수/매도 로직 구현 및 개선 과정
- [BE] Queue, Load Balancing, Redis