Skip to content

Commit

Permalink
refactor: 토큰 만료 처리하는 부분 리팩토링 (#148)
Browse files Browse the repository at this point in the history
* refactor: 토큰 리프레시 코드 리팩토링

* feat: 로그인 페이지 접속시 accessToken 존재 여부 뿐만 아니라 refreshToken 도 확인하도록 변경

* chore: 테스트용 코드 제거

* refactor: 적절한 함수명으로 변경 (verifyLogin -> verifyLoggedIn)

* feat: refreshTokens는 커스텀 axios 사용하지 않도록 변경
  • Loading branch information
syoung125 authored Jun 18, 2023
1 parent 7eb7b6d commit 004dd56
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 53 deletions.
15 changes: 7 additions & 8 deletions src/pages/login/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { mapoFlowerIsland } from '@/assets/styles/fonts';
import Icon from '@/shared/components/Icon';
import PageLayout from '@/shared/components/PageLayout';
import useAuth from '@/shared/hooks/useAuth';
import { getAccessToken } from '@/shared/utils/auth';

import styles from './index.module.scss';

Expand All @@ -15,15 +14,15 @@ const cx = classNames.bind(styles);
const Login = () => {
const router = useRouter();

const { loginWithKakao, loginWithNaver } = useAuth();
const { loginWithKakao, loginWithNaver, verifyLoggedIn } = useAuth();

useEffect(() => {
const accessToken = getAccessToken();
if (accessToken) {
router.push('/');
return;
}
}, [router]);
verifyLoggedIn().then((isLoggedIn) => {
if (isLoggedIn) {
router.push('/');
}
});
}, [router, verifyLoggedIn]);

return (
<PageLayout className={cx('main')}>
Expand Down
5 changes: 3 additions & 2 deletions src/shared/apis/auth/kakaoLogin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import axios from 'axios';
import { NEXT_PUBLIC_API_BASE_URI } from '@/shared/constants';
import { generateUrl } from '@/shared/utils/generateUrl';

export const kakaoLoginCallback = (code: string) =>
axios.request({
export const kakaoLoginCallback = (code: string) => {
return axios({
baseURL: NEXT_PUBLIC_API_BASE_URI,
url: generateUrl({ url: '/kakao', params: { code } }),
method: 'get',
validateStatus: null,
});
};
41 changes: 41 additions & 0 deletions src/shared/apis/auth/refreshTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import axios from 'axios';

import { NEXT_PUBLIC_API_BASE_URI } from '@/shared/constants';
import {
getRefreshToken,
getTokensFromResponse,
setAccessToken,
setRefreshToken,
} from '@/shared/utils/auth';

/**
* 리프레시 토큰을 사용하여 새 액세스 토큰을 가져옴
* @returns 토근 리프레시 성공 여부
*/
export const refreshTokens = async (): Promise<boolean> => {
try {
const refreshToken = getRefreshToken();

if (!refreshToken) {
throw new Error('refreshToken is undefined');
}

const response = await axios({
baseURL: NEXT_PUBLIC_API_BASE_URI,
url: '/token/refresh',
method: 'get',
headers: {
Authorization: `Bearer ${refreshToken}`,
},
validateStatus: null,
});

const tokens = getTokensFromResponse(response);
setAccessToken(tokens.accessToken);
setRefreshToken(tokens.refreshToken);

return true;
} catch (error) {
return false;
}
};
52 changes: 11 additions & 41 deletions src/shared/configs/axios.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import axios, { AxiosError, AxiosResponse } from 'axios';

import { refreshTokens } from '@/shared/apis/auth/refreshTokens';
import { NEXT_PUBLIC_API_BASE_URI } from '@/shared/constants';

import {
getAccessToken,
getRefreshToken,
getTokensFromResponse,
setAccessToken,
setRefreshToken,
} from '../utils/auth';
import { getAccessToken, getRefreshToken } from '../utils/auth';

import registerLogger from './logger';

Expand All @@ -18,33 +13,6 @@ let isFetchingAccessToken = false;
// 새 액세스 토큰을 기다리는 구독자 배열
let subscribers: ((accessToken: string) => Promise<void>)[] = [];

// 리프레시 토큰을 사용하여 새 액세스 토큰을 가져 오는 함수
async function refreshAccessToken(refreshToken: string) {
// 중복된 새로 고침 요청을 방지하기 위해 isFetchingAccessToken 플래그를 true로 설정
isFetchingAccessToken = true;

// 새로운 액세스 토큰을 가져 오기 위해 서버에 요청 보내기
const response = await instance.get('/token/refresh', {
validateStatus: null,
headers: {
Authorization: `Bearer ${refreshToken}`,
},
});
const tokens = getTokensFromResponse(response);

setAccessToken(tokens.accessToken);
setRefreshToken(tokens.refreshToken);

// isFetchingAccessToken 플래그를 false로 설정
isFetchingAccessToken = false;

// 새 액세스 토큰을 기다리는 모든 구독자를 호출하고 새 토큰 값을 전달
subscribers.forEach((callback) => callback(tokens.accessToken));

// 구독자 배열 지우기
subscribers = [];
}

const handleUnauthorizedError = async (error: AxiosError) => {
if (error.response?.status === 401) {
try {
Expand All @@ -69,15 +37,17 @@ const handleUnauthorizedError = async (error: AxiosError) => {
}
);

const refreshToken = getRefreshToken();
if (!isFetchingAccessToken) {
isFetchingAccessToken = true;
await refreshTokens();
isFetchingAccessToken = false;

if (!refreshToken) {
throw new Error('refreshToken is undefined');
}
const accessToken = getAccessToken()!;
// 새 액세스 토큰을 기다리는 모든 구독자를 호출하고 새 토큰 값을 전달
subscribers.forEach((callback) => callback(accessToken));

// 새 액세스 토큰을 이미 가져 오는 중이 아니면 새 액세스 토큰 가져 오기
if (!isFetchingAccessToken) {
refreshAccessToken(refreshToken);
// 구독자 배열 지우기
subscribers = [];
}

// 새 액세스 토큰을 가져 온 후 원래 요청을 다시 시도하기 위한 프로미스 반환
Expand Down
18 changes: 17 additions & 1 deletion src/shared/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ import {
NEXT_PUBLIC_KAKAO_REDIRECT_URI,
NEXT_PUBLIC_KAKAO_SCOPE,
} from '@/shared/constants';
import { removeAccessToken, removeRefreshToken } from '@/shared/utils/auth';
import {
getAccessToken,
removeAccessToken,
removeRefreshToken,
} from '@/shared/utils/auth';
import { generateUrl } from '@/shared/utils/generateUrl';

import { refreshTokens } from '../apis/auth/refreshTokens';

const useAuth = () => {
const router = useRouter();

Expand Down Expand Up @@ -38,10 +44,20 @@ const useAuth = () => {
router.push('/login');
};

const verifyLoggedIn = async (): Promise<boolean> => {
const accessToken = getAccessToken();
if (accessToken) {
return true;
}

return await refreshTokens();
};

return {
loginWithKakao,
loginWithNaver,
logout,
verifyLoggedIn,
};
};

Expand Down
2 changes: 1 addition & 1 deletion src/shared/utils/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ import { AxiosRequestConfig, AxiosResponse } from 'axios';

import axios from '@/shared/configs/axios';

export const request = <T>(config: AxiosRequestConfig) =>
export const request = <T = any>(config: AxiosRequestConfig) =>
axios(config).then((response: AxiosResponse<T>) => response.data);

0 comments on commit 004dd56

Please sign in to comment.