
최근 백엔드 팀에서 토큰 기반 인증 방식의 변경 요청이 있었습니다.
기존에는 단일 토큰을 사용하여 인증을 처리했는데, 이를 accessToken과 refreshToken을 사용하는 더 안전하고 효율적인 방식으로 변경하게 되었습니다.
토큰 만료 시간 차등 적용
로그인 응답 구조 변경
API 인증 프로세스 변경
API 요청과 응답을 처리하는 인터셉터를 구현하여 토큰 관리를 자동화했습니다.
// api/axios-interceptor.ts
const axiosInstance = axios.create({
baseURL: "http://13.125.58.70:8080",
timeout: 5000,
headers: {
"Content-Type": "application/json",
},
});
// Request Interceptor
axiosInstance.interceptors.request.use(
async (config) => {
// 인증이 필요없는 요청 처리
if (
config.url === "/auth/login" ||
config.url === "/auth/signup" ||
config.url === "/auth/refresh"
) {
return config;
}
// 토큰 확인 및 헤더 추가
const token = await AsyncStorage.getItem("accessToken");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}
);
accessToken 만료 시 자동으로 refreshToken을 사용하여 새로운 토큰을 발급받습니다.
// Response Interceptor
axiosInstance.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = await AsyncStorage.getItem("refreshToken");
const response = await axios.post("/auth/refresh", { refreshToken });
const { accessToken, refreshToken: newRefreshToken } = response.data;
await AsyncStorage.multiSet([
["accessToken", accessToken],
["refreshToken", newRefreshToken],
]);
originalRequest.headers.Authorization = `Bearer ${accessToken}`;
return axiosInstance(originalRequest);
} catch (refreshError) {
// refreshToken 만료 시 로그아웃 처리
await AsyncStorage.multiRemove([
"jti",
"accessToken",
"refreshToken",
"isLoggedIn",
]);
navigate("Login");
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
로그인 성공 시 받은 토큰들을 안전하게 저장합니다.
export const handleLogin = async ({
email,
password,
navigation,
axiosInstance,
onError,
onSuccess,
}: LoginParams): Promise<boolean> => {
try {
const response = await axiosInstance.post<LoginResponse>("/auth/login", {
email,
password,
});
if (response.data?.accessToken) {
const decodedToken = decodeToken(response.data.accessToken);
const userId = decodedToken?.userId;
await AsyncStorage.multiSet([
["jti", response.data.jti],
["accessToken", response.data.accessToken],
["refreshToken", response.data.refreshToken],
["isLoggedIn", "true"],
["userId", userId?.toString() ?? ""],
]);
return true;
}
return false;
} catch (error) {
// 에러 처리
return false;
}
};
로그아웃 시 모든 인증 관련 데이터를 안전하게 제거합니다.
export const useLogout = () => {
const navigation = useNavigation<TNavigationProp>();
const handleLogout = async () => {
try {
await AsyncStorage.multiRemove([
"jti",
"isLoggedIn",
"accessToken",
"refreshToken",
"userId",
]);
navigation.reset({
index: 0,
routes: [{ name: "Start" }],
});
} catch (error) {
console.error("로그아웃 실패:", error);
}
};
return handleLogout;
};
보안성 강화
사용자 경험 개선
유지보수성 향상
이번 인증 방식 변경을 통해 보안성과 사용자 경험을 모두 개선할 수 있었습니다.
특히 토큰 갱신 프로세스를 자동화함으로써 프론트엔드 로직이 더욱 안정적이고 견고해졌습니다.
향후 계획으로는 토큰 관리에 대한 모니터링 강화와 에러 처리 개선을 고려하고 있습니다.