Skip to content

Commit 606e7b8

Browse files
authored
Merge pull request #181 from GoHawaiiForMe/feat/#176리프레시
Feat/#리프레시
2 parents 0126c2c + 4afac89 commit 606e7b8

5 files changed

Lines changed: 64 additions & 20 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
# next.js
1111
/.next/
1212
.next
13+
/out/
1314

1415
# production
1516
/build

next.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import type { NextConfig } from "next";
33
const nextConfig: NextConfig = {
44
trailingSlash: true,
55
reactStrictMode: true,
6-
output: "export",
76
images: {
87
unoptimized: true,
98
remotePatterns: [

src/features/LoginForm.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ export default function LoginForm() {
5252
profileInfo.image,
5353
);
5454
router.replace("/");
55-
router.reload();
5655
} catch (error) {
5756
console.error("유저 정보 가져오기 실패", error);
5857
}

src/services/apiClient.ts

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import axios from "axios";
1+
import axios, { InternalAxiosRequestConfig } from "axios";
22
import { getAccessToken, removeAccessToken } from "@/utils/tokenUtils";
33
import authService from "./authService";
44
import router from "next/router";
5-
import useAuthStore from "@/stores/useAuthStore";
65

76
const apiClient = axios.create({
87
baseURL: process.env.NEXT_PUBLIC_API_URL,
@@ -11,12 +10,31 @@ const apiClient = axios.create({
1110
},
1211
});
1312

13+
let isRefreshing = false;
14+
let failedQueue: Array<{
15+
resolve: (token: string) => void;
16+
reject: (error: any) => void;
17+
}> = [];
18+
19+
const processQueue = (error: any, token: string | null = null) => {
20+
failedQueue.forEach((prom) => {
21+
if (error) {
22+
prom.reject(error);
23+
} else if (token) {
24+
prom.resolve(token);
25+
}
26+
});
27+
failedQueue = [];
28+
};
29+
1430
// request
1531
apiClient.interceptors.request.use(
16-
(config) => {
17-
const accessToken = getAccessToken();
18-
if (accessToken) {
19-
config.headers["Authorization"] = `Bearer ${accessToken}`;
32+
(config: InternalAxiosRequestConfig<any> & { _retry?: boolean }) => {
33+
if (config.url !== "/auth/refresh/token" && !config._retry) {
34+
const accessToken = getAccessToken();
35+
if (accessToken) {
36+
config.headers["Authorization"] = `Bearer ${accessToken}`;
37+
}
2038
}
2139
return config;
2240
},
@@ -29,20 +47,39 @@ apiClient.interceptors.request.use(
2947
apiClient.interceptors.response.use(
3048
(response) => response,
3149
async (error) => {
32-
if (error.response && error.response?.status === 401) {
50+
const originalRequest = error.config;
51+
52+
if (error.response?.status === 401 && !originalRequest._retry) {
53+
if (isRefreshing) {
54+
try {
55+
const token = await new Promise<string>((resolve, reject) => {
56+
failedQueue.push({ resolve, reject });
57+
});
58+
originalRequest.headers["Authorization"] = `Bearer ${token}`;
59+
return apiClient(originalRequest);
60+
} catch (err) {
61+
return Promise.reject(err);
62+
}
63+
}
64+
65+
originalRequest._retry = true;
66+
isRefreshing = true;
67+
3368
try {
34-
const newAccessToken = await authService.refreshToken();
35-
alert("새 토큰 발급!");
36-
error.config.headers["Authorization"] = `Bearer ${newAccessToken}`;
37-
return apiClient(error.config);
38-
} catch (error: any) {
39-
console.error("토큰 갱신 실패", error);
69+
const token = await authService.refreshToken();
70+
originalRequest.headers["Authorization"] = `Bearer ${token}`;
71+
processQueue(null, token);
72+
return apiClient(originalRequest);
73+
} catch (refreshError) {
74+
processQueue(refreshError, null);
4075
removeAccessToken();
4176
router.push("/login");
42-
alert("새로 로그인해주세요!");
43-
return Promise.reject(error);
77+
return Promise.reject(refreshError);
78+
} finally {
79+
isRefreshing = false;
4480
}
4581
}
82+
4683
return Promise.reject(error);
4784
},
4885
);

src/services/authService.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { setAccessToken } from "@/utils/tokenUtils";
1+
import { removeAccessToken, setAccessToken } from "@/utils/tokenUtils";
22
import { api } from "./api";
33
import { BAD_REQUEST, INTERNAL_SERVER_ERROR, UNAUTHORIZED } from "@/utils/errorStatus";
4+
import router from "next/router";
5+
import useAuthStore from "@/stores/useAuthStore";
46

57
interface OAuthResponse {
68
provider?: string;
@@ -59,6 +61,7 @@ const authService = {
5961
const response = await api.post<LoginResponse, { email: string; password: string }>(
6062
"/auth/login",
6163
data,
64+
true,
6265
);
6366
setAccessToken(response.accessToken);
6467

@@ -105,7 +108,7 @@ const authService = {
105108
},
106109
refreshToken: async () => {
107110
try {
108-
const response: RefreshTokenResponse = await api.post("/auth/refresh/token", true); //withCrediential
111+
const response: RefreshTokenResponse = await api.post("/auth/refresh/token", {}, true);
109112
const newAccessToken = response.accessToken;
110113

111114
if (!newAccessToken) {
@@ -114,7 +117,12 @@ const authService = {
114117

115118
return newAccessToken;
116119
} catch (error: any) {
117-
console.error("토큰 갱신 실패", error);
120+
if (error.response?.status === 400) {
121+
alert("로그인 정보가 유실되었습니다. 다시 로그인해주세요!");
122+
removeAccessToken();
123+
useAuthStore.getState().setLogout();
124+
router.push("/login");
125+
}
118126
throw error;
119127
}
120128
},

0 commit comments

Comments
 (0)