728x90
🍪axios interceptors 설정하기(feat.cookie)
1. axios 인터셉터 설정
const axiosInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
headers: {
'Content-Type': 'application/json',
},
});
2. 요청 인터셉터 설정
axiosInstance.interceptors.request.use(
async (config: InternalAxiosRequestConfig) => {
const excludedUrls = ['/auth/tokens'];
if (excludedUrls.some((url) => config.url?.includes(url))) {
return config;
}
const accessToken = getCookie('accessToken');
if (accessToken) {
config.headers['Authorization'] = `Bearer ${accessToken}`;
}
return config;
},
(error) => Promise.reject(error),
);
- 요청 인터셉터는 모든 요청이 서버에 보내지기 전에 호출된다.
- excludedUrls 배열에 포함된 URL에 대해서는 토큰을 추가하지 않는다 여기서 내 api는 /auth/tokens URL이 제외되는걸로...
- getCookie 함수를 사용하여 쿠키에서 accessToken을 가져온다. 만약 토큰이 존재하면 요청 헤더에 Authorization 필드를 추가한다.
❓왜 excludedUrls 하는가
auth/tokens와 같은 URL은 보통 새로운 액세스 토큰을 요청하거나 리프레시 토큰을 사용하는 요청이다. 이러한 요청에서는 이미 리프레시 토큰이 Authorization 헤더에 포함되어 있어야 하고, 액세스 토큰을 추가하면 오류가 발생할 수 있다.만약 요청 인터셉터가 모든 요청에 대해 액세스 토큰을 추가한다면, /auth/tokens에 대한 요청이 진행될 때 이미 만료된 액세스 토큰을 사용하려고 시도할 수 있다. 이 경우 서버는 401 오류를 반환하고, 응답 인터셉터에서 다시 토큰을 갱신하려고 시도하면서 무한 루프가 발생할 수 있다.
3. 리프레시 토큰 함수
const refreshToken = async () => {
const refreshToken = getCookie('refreshToken');
if (!refreshToken) {
throw new Error('리프레시 토큰이 없습니다.');
}
try {
const response = await axiosInstance.post(
'/auth/tokens',
{},
{
headers: {
Authorization: `Bearer ${refreshToken}`,
},
},
);
const { accessToken, refreshToken: newRefreshToken } = response.data;
// 새로운 토큰 저장
setCookie('accessToken', accessToken);
setCookie('refreshToken', newRefreshToken);
axiosInstance.defaults.headers.common['Authorization'] =
`Bearer ${accessToken}`;
return accessToken;
} catch (error) {
deleteCookie('accessToken');
deleteCookie('refreshToken');
if (typeof window !== 'undefined') {
alert('다시 로그인해주세요.');
}
throw error;
}
};
- 액세스 토큰이 만료되었을 때 호출되어 새로운 액세스 토큰을 요청한다.
- 쿠키에서 refreshToken을 가져오고, 없으면 오류를 발생시킨다.
- /auth/tokens 엔드포인트에 POST 요청을 보내어 새로운 토큰을 요청한다.
- refreshToken을 Authorization 헤더에 추가해 보냄
- 서버로부터 새로운 accessToken과 refreshToken을 받아서 쿠키에 저장한다.
- 인스턴스의 기본 헤더에 새로운 액세스 토큰을 설정한다.
4. 응답 인터셉터 설정
axiosInstance.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
const originalRequest = error.config as InternalAxiosRequestConfig;
if (error.response?.status === 401) {
try {
const accessToken = await refreshToken();
// 새로운 액세스 토큰으로 헤더 업데이트 및 원래 요청 재시도
originalRequest.headers['Authorization'] = `Bearer ${accessToken}`;
return axiosInstance(originalRequest);
} catch (error) {
return Promise.reject(error);
}
}
return Promise.reject(error);
},
);
- 응답 인터셉터는 서버의 응답을 가로채 처리한다.
- 응답이 성공적이면 그대로 반환한다.
- 만약 응답의 상태 코드가 401 (Unauthorized)일 경우, refreshToken 함수를 호출해 새로운 액세스 토큰을 요청한다.
- 새로운 액세스 토큰을 받고, 원래 요청의 헤더를 업데이트한 후, 원래의 요청을 다시 시도한다.
풀코드
import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';
import { deleteCookie, getCookie, setCookie } from 'cookies-next';
const axiosInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
headers: {
'Content-Type': 'application/json',
},
});
axiosInstance.interceptors.request.use(
async (config: InternalAxiosRequestConfig) => {
const excludedUrls = ['/auth/tokens'];
if (excludedUrls.some((url) => config.url?.includes(url))) {
return config;
}
const accessToken = getCookie('accessToken');
if (accessToken) {
config.headers['Authorization'] = `Bearer ${accessToken}`;
}
return config;
},
(error) => Promise.reject(error),
);
const refreshToken = async () => {
const refreshToken = getCookie('refreshToken');
if (!refreshToken) {
throw new Error('리프레시 토큰이 없습니다.');
}
try {
const response = await axiosInstance.post(
'/auth/tokens',
{},
{
headers: {
Authorization: `Bearer ${refreshToken}`,
},
},
);
const { accessToken, refreshToken: newRefreshToken } = response.data;
// 새로운 토큰 저장
setCookie('accessToken', accessToken);
setCookie('refreshToken', newRefreshToken);
axiosInstance.defaults.headers.common['Authorization'] =
`Bearer ${accessToken}`;
return accessToken;
} catch (error) {
deleteCookie('accessToken');
deleteCookie('refreshToken');
if (typeof window !== 'undefined') {
alert('다시 로그인해주세요.');
}
throw error;
}
};
axiosInstance.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
const originalRequest = error.config as InternalAxiosRequestConfig;
if (error.response?.status === 401) {
try {
const accessToken = await refreshToken();
// 새로운 액세스 토큰으로 헤더 업데이트 및 원래 요청 재시도
originalRequest.headers['Authorization'] = `Bearer ${accessToken}`;
return axiosInstance(originalRequest);
} catch (error) {
return Promise.reject(error);
}
}
return Promise.reject(error);
},
);
export default axiosInstance;
'🔥Next 뽀개기' 카테고리의 다른 글
Next.js에 firebase 연동해서 회원가입 구현하기 (0) | 2024.11.29 |
---|---|
Zod + React Hook Form = 회원가입 뚝-딱! (0) | 2024.11.27 |
구글 간편 로그인 구현하기 (0) | 2024.09.07 |
카카오 간편 로그인 구현하기 (4) | 2024.08.24 |
Next.js 왜 씀? (0) | 2024.05.30 |