Skip to content

Commit 2f48aad

Browse files
authored
Merge pull request #520 from meowzip/reese
fix: cookie deletion fails on logout in production
2 parents 11448ea + af34d85 commit 2f48aad

File tree

3 files changed

+85
-25
lines changed

3 files changed

+85
-25
lines changed

src/app/api/auth/logout/route.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,38 @@
11
import { NextResponse } from 'next/server';
22

3+
const getCookieDomain = () => {
4+
const isProduction = process.env.NODE_ENV === 'production';
5+
6+
if (!isProduction) {
7+
return 'localhost';
8+
}
9+
10+
return '.meowzip.com';
11+
};
12+
313
export async function POST() {
414
const response = NextResponse.json({ success: true });
515

616
const isProduction = process.env.NODE_ENV === 'production';
7-
const isLocalhost = !isProduction;
17+
const cookieDomain = getCookieDomain();
818

919
const baseConfig = {
1020
secure: isProduction,
1121
path: '/',
12-
...(isLocalhost && { domain: 'localhost' })
22+
domain: cookieDomain
1323
};
1424

1525
const cookieConfigs = {
1626
Authorization: {
1727
...baseConfig,
18-
secure: true
28+
secure: true,
29+
sameSite: 'lax' as const
1930
},
2031
'Authorization-Refresh': {
2132
...baseConfig,
22-
secure: true
33+
secure: true,
34+
sameSite: 'lax' as const
2335
},
24-
2536
'next-auth.session-token': {
2637
...baseConfig,
2738
sameSite: 'lax' as const

src/utils/fetch.ts

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ const isTokenExpired = (token: string): boolean => {
4242
let isRefreshing = false;
4343
let refreshPromise: Promise<string | null> | null = null;
4444

45+
const getRefreshTokenUrl = (): string => {
46+
if (typeof window === 'undefined') {
47+
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL;
48+
return `${baseUrl}/api/auth/refresh-token`;
49+
}
50+
return '/api/auth/refresh-token';
51+
};
52+
4553
const refreshTokenIfNeeded = async (): Promise<string | null> => {
4654
if (isRefreshing && refreshPromise) {
4755
return refreshPromise;
@@ -50,10 +58,33 @@ const refreshTokenIfNeeded = async (): Promise<string | null> => {
5058
isRefreshing = true;
5159
refreshPromise = (async () => {
5260
try {
53-
const response = await fetch('/api/auth/refresh-token', {
61+
const refreshUrl = getRefreshTokenUrl();
62+
const requestOptions: RequestInit = {
5463
method: 'POST',
5564
credentials: 'include'
56-
});
65+
};
66+
67+
if (typeof window === 'undefined') {
68+
try {
69+
const { cookies } = await import('next/headers');
70+
const cookieStore = cookies();
71+
const refreshToken = cookieStore.get('Authorization-Refresh')?.value;
72+
73+
if (!refreshToken) {
74+
console.error('서버 사이드: Refresh Token이 없습니다');
75+
return null;
76+
}
77+
78+
requestOptions.headers = {
79+
Cookie: `Authorization-Refresh=${refreshToken}`
80+
};
81+
} catch (error) {
82+
console.error('서버 사이드 쿠키 읽기 실패:', error);
83+
return null;
84+
}
85+
}
86+
87+
const response = await fetch(refreshUrl, requestOptions);
5788

5889
if (response.ok) {
5990
const data = await response.json();
@@ -105,7 +136,9 @@ export const fetchAuth = returnFetch({
105136
];
106137
},
107138
response: async (response, [url, requestInit], fetch) => {
108-
if (response.status === 401) {
139+
const isRetry = (requestInit as any)?._isRetry;
140+
141+
if (response.status === 401 && !isRetry) {
109142
const newToken = await refreshTokenIfNeeded();
110143
if (newToken) {
111144
const headers = new Headers(requestInit?.headers);
@@ -114,10 +147,16 @@ export const fetchAuth = returnFetch({
114147
const retryResponse = await fetch(url, {
115148
...requestInit,
116149
headers,
117-
credentials: 'include'
118-
});
150+
credentials: 'include',
151+
_isRetry: true // 재시도 플래그 추가
152+
} as any);
119153

120154
return retryResponse;
155+
} else {
156+
if (typeof window !== 'undefined') {
157+
console.error('세션이 만료되었습니다. 다시 로그인해주세요.');
158+
window.location.href = '/signin';
159+
}
121160
}
122161
}
123162

@@ -155,7 +194,9 @@ export const fetchAuthJson = returnFetchJson({
155194
];
156195
},
157196
response: async (response, [url, requestInit], fetch) => {
158-
if (response.status === 401) {
197+
const isRetry = (requestInit as any)?._isRetry;
198+
199+
if (response.status === 401 && !isRetry) {
159200
const newToken = await refreshTokenIfNeeded();
160201
if (newToken) {
161202
const headers = new Headers(requestInit?.headers);
@@ -164,10 +205,16 @@ export const fetchAuthJson = returnFetchJson({
164205
const retryResponse = await fetch(url, {
165206
...requestInit,
166207
headers,
167-
credentials: 'include'
168-
});
208+
credentials: 'include',
209+
_isRetry: true
210+
} as any);
169211

170212
return retryResponse;
213+
} else {
214+
if (typeof window !== 'undefined') {
215+
console.error('세션이 만료되었습니다. 다시 로그인해주세요.');
216+
window.location.href = '/signin';
217+
}
171218
}
172219
}
173220

src/utils/returnFetch.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -107,13 +107,21 @@ const returnFetch =
107107

108108
const response = await fetchProvided(...requestInterceptorAppliedArgs);
109109

110-
if (!response.ok) {
111-
const errorText = await response.clone().text();
112-
const msg = `STATUS: ${response.status}
110+
const finalResponse = defaultOptions?.interceptors?.response
111+
? await defaultOptions.interceptors.response(
112+
response,
113+
requestInterceptorAppliedArgs,
114+
fetchProvided
115+
)
116+
: response;
117+
118+
if (!finalResponse.ok) {
119+
const errorText = await finalResponse.clone().text();
120+
const msg = `STATUS: ${finalResponse.status}
113121
ERROR_TEXT: ${errorText}`;
114-
await sendDiscordErrorLog(msg, response.url);
122+
await sendDiscordErrorLog(msg, finalResponse.url);
115123

116-
let errorMessage = `요청 실패 (상태 코드: ${response.status})`;
124+
let errorMessage = `요청 실패 (상태 코드: ${finalResponse.status})`;
117125
try {
118126
const errorData = JSON.parse(errorText);
119127
if (errorData.message) {
@@ -123,13 +131,7 @@ const returnFetch =
123131
throw new Error(errorMessage);
124132
}
125133

126-
return (
127-
defaultOptions?.interceptors?.response?.(
128-
response,
129-
requestInterceptorAppliedArgs,
130-
fetchProvided
131-
) || response
132-
);
134+
return finalResponse;
133135
};
134136

135137
export default returnFetch;

0 commit comments

Comments
 (0)