1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
|
/**
* 커스텀 로그아웃 함수
* NextAuth의 signOut이 NEXTAUTH_URL로 강제 리다이렉트하는 문제를 해결하기 위해 직접 구현
*/
interface CustomSignOutOptions {
callbackUrl?: string;
redirect?: boolean;
}
/**
* 커스텀 로그아웃 함수
*
* @param options - callbackUrl: 로그아웃 후 이동할 URL (상대 경로 권장: "/ko/partners")
* @param options - redirect: 자동 리다이렉트 여부 (기본: true)
*/
export async function customSignOut(options?: CustomSignOutOptions): Promise<void> {
const { callbackUrl, redirect = true } = options || {};
console.log('[customSignOut] 시작:', {
currentOrigin: window.location.origin,
currentHref: window.location.href,
callbackUrl,
redirect,
});
try {
// 1. CSRF 토큰 가져오기
const csrfResponse = await fetch('/api/auth/csrf');
const { csrfToken } = await csrfResponse.json();
console.log('[customSignOut] CSRF 토큰 획득');
// 2. 서버에 로그아웃 요청 (리다이렉트 방지)
const signoutResponse = await fetch('/api/auth/signout', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
csrfToken,
json: 'true',
}),
redirect: 'manual', // ⭐ 서버의 리다이렉트를 자동으로 따라가지 않음
});
console.log('[customSignOut] 서버 응답:', {
status: signoutResponse.status,
statusText: signoutResponse.statusText,
redirected: signoutResponse.redirected,
url: signoutResponse.url,
});
// 3. NextAuth 세션 쿠키 즉시 삭제 (middleware가 감지하도록)
document.cookie = 'next-auth.session-token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
document.cookie = '__Secure-next-auth.session-token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Secure;';
console.log('[customSignOut] 세션 쿠키 삭제 완료');
// 4. 리다이렉트
if (redirect) {
// ⭐ URL 객체로 변환하여 상대 경로인지 확인
let finalUrl: string;
if (callbackUrl) {
try {
// callbackUrl이 절대 URL인 경우 (http:// 또는 https://로 시작)
if (callbackUrl.startsWith('http://') || callbackUrl.startsWith('https://')) {
const urlObj = new URL(callbackUrl);
// 같은 origin인 경우 pathname만 사용 (상대 경로로 변환)
if (urlObj.origin === window.location.origin) {
finalUrl = urlObj.pathname + urlObj.search + urlObj.hash;
} else {
finalUrl = callbackUrl;
}
} else {
// 이미 상대 경로인 경우 (/, /ko/partners 등)
finalUrl = callbackUrl;
}
} catch (error) {
console.error('[customSignOut] callbackUrl 파싱 오류:', error);
finalUrl = callbackUrl; // 오류 시 원본 사용
}
} else {
// callbackUrl이 없는 경우 루트 경로로
finalUrl = '/';
}
console.log('[customSignOut] 리다이렉트 실행:', finalUrl);
window.location.href = finalUrl;
}
} catch (error) {
console.error('Custom sign out error:', error);
// 에러 발생 시에도 리다이렉트 (세션이 이미 만료되었을 수 있음)
if (redirect) {
let finalUrl = '/';
if (callbackUrl) {
try {
if (callbackUrl.startsWith('http://') || callbackUrl.startsWith('https://')) {
const urlObj = new URL(callbackUrl);
if (urlObj.origin === window.location.origin) {
finalUrl = urlObj.pathname + urlObj.search + urlObj.hash;
} else {
finalUrl = callbackUrl;
}
} else {
finalUrl = callbackUrl;
}
} catch {
finalUrl = callbackUrl;
}
}
window.location.href = finalUrl;
}
}
}
|