→ 현재 개발중인 서비스는 사용자 인증이 된 사용자만 사용할 수 있기에, 모든 axios 요청시 header에 Authorize: Bearer {토큰 값}
을 넣어서 요청해야했다. 매번 요청시 Header에 넣을 수 없기에 mutation을 통해 전역으로 axios의 header에 Authorize: Bearer {토큰 값}
을 넣거나 빼기로 하였다.
→ 나의 경우, utils/auth/orval/setAuthToken.ts 에 작성했지만 파일의 위치는 상관없다.
- 앱 재시작을 고려하여
setToken
을 할때 AsyncStorage 에 데이터를 저장합니다.- App.tsx와 setAuthToken.ts에서
axios.defaults.baseURL = BASE_URL;
를 통해 baseURL을 지정합니다.axios.interceptors.response.use
통해 토큰 만료시refreshAccessToken()
를 실행합니다.
- 해당 함수(refreshAccessToken)는 미완성 → API 완성 후 가능 or 재로그인 하여 토큰 재발행
// axiosConfig.ts
import AsyncStorage from "@react-native-async-storage/async-storage";
import axios from "axios";
import { BASE_URL } from "../../../constants/axios";
axios.defaults.baseURL = BASE_URL;
export const setAuthHeader = async (token: string) => {
axios.defaults.headers.common.Authorization = `Bearer ${token}`;
return AsyncStorage.setItem("@auth_token", token).catch((e) =>
console.error("setAuthToken.ts Error while setting or getting token:", e)
);
};
export const clearAuthHeader = () => (axios.defaults.headers.common.Authorization = undefined);
export const getToken = async () => (await AsyncStorage.getItem("@auth_token")) || null;
export const initToken = async () => {
const authToken = await AsyncStorage.getItem("@auth_token").catch((e) => console.error("getAuthToken Error:", e));
authToken ? setAuthHeader(authToken) : clearAuthHeader();
};
// 1. 초기화
// axios.interceptors.request.use((config: InternalAxiosRequestConfig) => config);
// 2. 만약 만료된 토큰일 떄 refreshAccessToklen로 갱신
axios.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// 토큰이 만료되었는지 확인
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true; // 이 플래그는 같은 요청을 중복으로 재시도하지 않도록 함
// TODO: refreshToken을 사용하여 accessToken 갱신
const refreshedToken = "";
// const refreshedToken = await refreshAccessToken();
// 토큰 저장 및 헤더 업데이트
setAuthHeader(refreshedToken);
// 원래의 요청 재시도
return axios(originalRequest);
}
return Promise.reject(error);
}
);
// 실제 적용한 코드
import AsyncStorage from "@react-native-async-storage/async-storage";
import axios from "axios";
import { BASE_URL } from "../../../constants/axios";
import { getNewRefreshTokenData } from "../cognito";
import { CognitoUserSession } from "amazon-cognito-identity-js";
axios.defaults.baseURL = BASE_URL;
//토큰 저장
export const setAuthHeader = async ({
accessToken: at,
refreshToken: rt,
idToken: it,
}: {
accessToken: string;
refreshToken: string;
idToken: string;
}) => {
axios.defaults.headers.common.Authorization = `Bearer ${it}`;
await AsyncStorage.setItem("@id_token", it).catch((e) =>
console.error("setAuthHeader Err @id_token:", e)
);
await AsyncStorage.setItem("@access_token", at).catch((e) =>
console.error("setAuthHeader Err @access_token:", e)
);
await AsyncStorage.setItem("@refresh_token", rt).catch((e) =>
console.error("setAuthHeader Err @refresh_token:", e)
);
// 10ms 대기하는 함수를 Promise로 감싸기
const delay = (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms));
await delay(20); //axios.defaults.headers set 하는데 시간 좀 걸려서 해놓음
return;
};
//토큰 가져오기
export const getAuthHeader = async () => {
const it = await AsyncStorage.getItem("@id_token").catch((e) =>
console.error("getAuthHeader Err @id_token:", e)
);
const at = await AsyncStorage.getItem("@access_token").catch((e) =>
console.error("getAuthHeader Err @access_token:", e)
);
const rt = await AsyncStorage.getItem("@refresh_token").catch((e) =>
console.error("getAuthHeader Err @refresh_token:", e)
);
return { accessToken: at, refreshToken: rt, idToken: it };
};
//토큰 삭제
export const clearAuthHeader = async () => {
await AsyncStorage.clear();
axios.defaults.headers.common.Authorization = undefined;
// 10ms 대기하는 함수를 Promise로 감싸기
const delay = (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms));
await delay(20); //axios.defaults.headers set 하는데 시간 좀 걸려서 해놓음
};
//토큰 axios에 적용하기
export const initToken = async () => {
const idToken = await AsyncStorage.getItem("@id_token").catch((e) =>
console.error("getAuthToken Error:", e)
);
idToken
? (axios.defaults.headers.common.Authorization = `Bearer ${idToken}`)
: clearAuthHeader();
};
// refresh token이 재기능을 하는지 확인
const checkToken = async (session: CognitoUserSession) => {
const { accessToken, refreshToken, idToken } = await getAuthHeader();
const isEqualRefreshToken =
session.getRefreshToken().getToken() === refreshToken;
const isEqualIdToken = session.getIdToken().getJwtToken() === idToken;
const isEqualAccessToken =
session.getAccessToken().getJwtToken() === accessToken;
const message = `isEqualRefreshToken: ${isEqualRefreshToken} \n isEqualIdToken: ${isEqualIdToken} isEqualAccessToken: ${isEqualAccessToken}`;
alert(message);
};
//토큰 만료시 처리
axios.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// axios error가 아닌 경우
if (!axios.isAxiosError(error)) return Promise.reject(error);
// 토큰이 만료되었는지 확인
if (error.response?.status === 401 && !originalRequest?._retry) {
originalRequest._retry = true; // 이 플래그는 같은 요청을 중복으로 재시도하지 않도록 함
// TODO: refreshToken을 사용하여 accessToken 갱신
const token = await AsyncStorage.getItem("@refresh_token").catch((e) =>
console.error(
"setAuthToken.ts axios.interceptors Error @refresh_token:",
e
)
);
// TODO: refreshToken이 없는 경우 로그아웃 처리
if (!token) {
alert("Your login has expired. Please log in again.");
return clearAuthHeader();
}
const session: CognitoUserSession | null = await getNewRefreshTokenData(
token
).catch((e) => {
console.error(e);
return null;
});
// TODO: 회원이 존재하지 않는 경우 로그아웃 처리
if (session === null) {
alert("Your login has expired. Please log in again.");
return clearAuthHeader();
}
await checkToken(session);
// 토큰 저장 및 헤더 업데이트
setAuthHeader({
accessToken: session.getAccessToken().getJwtToken(),
refreshToken: session.getRefreshToken().getToken(),
idToken: session.getIdToken().getJwtToken(),
});
// 원래의 요청 재시도
return axios(originalRequest);
}
// 기타 axios 오류 처리
return Promise.reject(error);
}
);
→ App.tsx에 useEffect로 추가하기
- 기기에 저장된 토큰이 있는지 확인 후 전역으로 initToken을 해줍니다.
import axios from "axios";
import { clearAuthHeader, setAuthHeader } from "./utils/auth/orval/setAuthToken";
// axios의 전역 기본값 설정
axios.defaults.baseURL = BASE_URL;
const App = () => {
...
useEffect(() => {
const setFont = async () => {
...
};
setFont();
initToken();
}, []);