React Native ์ฑ์์ JWT ์ธ์ฆ์ ์ฌ์ฉํ๋ ์ค, accessToken์ด ๋ง๋ฃ๋์ด 401 ์๋ฌ๊ฐ ๋ฐ์ํ๋ ๋ฌธ์ ๊ฐ ์์๊ณ ,
์ด๋ก ์ธํด ํน์ ์์ฒญ์ด ์ฒ๋ฆฌ๋์ง ์๊ฑฐ๋ ์ฑ ๋์์ด ๋ฉ์ถ๋ ์ฌ๋ก๊ฐ ์์ฃผ ๋ฐ๊ฒฌ๋์์ต๋๋ค.
์ฒ์์๋ ํ ํฐ ๊ฐฑ์ ์ด ์ ์์ ์ผ๋ก ์๋ํ๋ค๊ณ ํ๋จํ์ง๋ง, ์ค์ ์๋น์ค์์๋ ๋์ ์์ฒญ ์ฒ๋ฆฌ, ์ฌ์๋ ๋๋ฝ, ๋ฌดํ ๋ฃจํ ๋ฑ ๋ค์ํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค๋ ๊ฒ์ ํ์ธํ์ต๋๋ค.
์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด, Axios ์ธํฐ์
ํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ํฐ์ ๊ฐฑ์ ํ๊ณ , ์คํจํ ์์ฒญ์ ์๋์ผ๋ก ์ฌ์๋ํ๋ ๊ตฌ์กฐ๋ฅผ ์ค๊ณํ์ต๋๋ค.
// apiClient.ts
import axios from 'axios';
export const apiClient = axios.create({ baseURL: BASE_URL });
apiClient.interceptors.request.use(
(config) => config,
(error) => Promise.reject(error)
);
apiClient.interceptors.response.use(
(response) => response,
(error) => handleJwtError(error, apiClient)
);
// handleJwtError.ts
export const handleJwtError = (error: unknown, client: AxiosInstance) => {
if (!(error instanceof AxiosError)) return;
const { config, response } = error;
const requestUrl = config?.url;
if (response?.status === 401) {
// refreshToken ์์ฒด ์์ฒญ์ธ ๊ฒฝ์ฐ ๋ฌดํ ๋ฃจํ ๋ฐฉ์ง
if (requestUrl?.includes('/auth/token/refresh')) {
return Promise.reject(error);
}
return attemptTokenRefreshAndRetry(config, client);
}
return Promise.reject(error);
};
let isRefreshing = false;
const pendingRequests: Array<{
resolve: (token: string) => void;
reject: (error: unknown) => void;
}> = [];
const flushQueue = (error: any, newToken: string | null = null) => {
pendingRequests.forEach(({ resolve, reject }) => {
if (error) reject(error);
else if (newToken) resolve(newToken);
});
pendingRequests.length = 0;
};
// attemptTokenRefreshAndRetry.ts
export const attemptTokenRefreshAndRetry = async (originalConfig: any, client: AxiosInstance) => {
if (!originalConfig) return Promise.reject(new Error('Missing config'));
if (isRefreshing) {
return new Promise((resolve, reject) => {
pendingRequests.push({ resolve, reject });
}).then((token) => {
originalConfig.headers.Authorization = `Bearer ${token}`;
return client(originalConfig);
});
}
isRefreshing = true;
try:
const refreshToken = await getRefreshTokenFromStorage();
if (!refreshToken) throw new Error('No refresh token found');
const { accessToken, refreshToken: newRefreshToken } = await requestNewToken(refreshToken);
await saveTokensToStorage(accessToken, newRefreshToken);
client.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
flushQueue(null, accessToken);
originalConfig.headers.Authorization = `Bearer ${accessToken}`;
return client(originalConfig);
} catch (err) {
flushQueue(err, null);
await clearTokenStorage();
handleLogout();
return Promise.reject(err);
} finally {
isRefreshing = false;
}
};
401 Unauthorized ์๋ต์ด ์ค๋ฉด attemptTokenRefreshAndRetry๋ก ์ง์
const getRefreshTokenFromStorage = () => SecureStore.getItemAsync('refresh_token');
const saveTokensToStorage = (access: string, refresh: string) => {
return Promise.all([
SecureStore.setItemAsync('access_token', access),
SecureStore.setItemAsync('refresh_token', refresh),
]);
};
const clearTokenStorage = () => {
return Promise.all([
SecureStore.deleteItemAsync('access_token'),
SecureStore.deleteItemAsync('refresh_token'),
]);
};
const handleLogout = () => {
store.dispatch(SignOut());
reset({ routes: [{ name: 'Auth', params: { screen: 'Landing' } }] });
};
> ๋ฐ๋ฆฐ๊ฑธ ๋ชฐ์์ฐ์ง ์๋๋ก ํ์...!