accessToken을 사용하여 refreshToken 재발급 받기

mia·2024년 2월 27일
0

로그인을 하면 백에서 accessToken과 refreshToken을 받는다.

매 요청마다 accessToken을 통해 인증, 인가를 거치는데 이 때 accessToken이 탈취될 경우를 대비해 accessToken의 기한을 짧게 설정하고 기한이 만료될 경우 refreshToken을 통해 accessToken을 재발급 받아 사용자의 불편함을 줄인다고 한다.

그렇다면 refreshToken을 통해 accessToken을 재발급받는 절차는 어떻게 진행할까?
어쨌든 사용자가 모르게 accessToken을 재발급 받아야 사용자의 불편함이 없을 것이다.

그래서 나의 경우 fetch 함수를 추가로 만들어 accessToken 만료 응답이 올 경우 refreshToken 재발급 절차를 거쳐 성공시 다시 fetch를 보내는 로직을 만들었다.

함수가 하는 일은 다음과 같다.

  1. accessToken 만료, refreshToken 유효 : accessToken 재발급 후 fetch 재 실행
  2. accessToken 만료, refreshToken 만료 : logout 처리 (로그인하면서 다시 accessToken과 refreshToken을 받기 때문)
  3. accessToken 유효, refreshToken 유효 : 문제 없이 fetch 함수 실행

// fetch.ts

export const fetchWithZod = (
	method,
	path,
	requestData,
	fetchOptions
) => {
	...
	// 헤더에 accessToken을 넣는 부분
	const headers = {};
	if(!isFiles) headers['Content-Type'] = 'application/json';

	if(typeof window !== 'undefined') {
		let accessToken;
		try {
			accessToken = JSON.parse(localStorage.getItem('token') as string).accessToken;
		} catch {
			accessToken = '';
		}
 
		if(accesstoken) headers['Authorization'] = `Bearer ${accessToken}`;
	}

	const init = {
		method,
		credentials: 'include',
		headers,
		cache: 'no-store',
	}

	const response = await fetch(url, init);
	const responseData = await response.json().catch(() => null);

	// fetch 응답 체크
	if(response.ok) {
		if(responseData === null)
			return {
				code: -1,
				message: 'response json parsing error',
				data: null
				}
			return responseData;
	}

	if(!response.ok) {
		if(responseData.code === 866) { // accessToken 만료 
			if(typeof window !== 'undefined') {
				let refreshToken;
				try {
					refreshToken = JSON.parse(
						localStorage.getItem('token') as string
					).refreshToken;
				} catch {
					refreshToken = "";
				}
				if(refreshToken) headers['Authorization'] = `Bearer ${refreshToken}`;
			}
			fetch(
				`${ServerURL}/refresh/token`,
				{
					method: 'POST',
					headers,
				}
			)
				.then(res => res.json())
				.then(res => {
					switch (res.code) {
						case 100: // accessToken 재발급 성공! response -> accessToken, refreshToken
							const tokenStr = JSON.stringify(res.data ?? '');
							localStorage.setItem('token', tokenStr);

							const headers = {};
							if(!isFiles) headers['Content-Type'] = 'application/json';
							headers['Authorization'] = `Bearer ${res.data.accessToken}`;
							init.headers = headers;
							// 재발급 받은 토큰을 사용하여 다시 요청 보내기
							return fetchWithZod(
								method,
								path,
								requestData,
								fetchOptions
							).catch(() => {
								return {
									code: -1,
									message: 'fetch response error',
								  data: null
								};
							});
						case 866: // refreshToken 만료
							// 로그아웃 처리
							localStorage?.removeItem('token');
							window?.location?.replace('/');
							return {
								code: -1,
								message: 'token Expired',
								data: null
							}
					}
				});
		} catch (error) {
			return {
				code: -1,
				message: 'fetch error',
				data: null
			}
		}
	}
}
profile
노 포기 킾고잉

0개의 댓글