Skip to content

Commit 1b71156

Browse files
committed
fix: 운영환경 인증절차 무한 리다이렉션 현상 - retry
1 parent 75b8d67 commit 1b71156

2 files changed

Lines changed: 81 additions & 69 deletions

File tree

‎packages/ui/src/auth/api-client.ts‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,18 @@ const apiClient = axios.create({
1212

1313
apiClient.interceptors.request.use(
1414
async (config) => {
15+
// 이미 헤더에 Authorization이 설정되어 있다면 (직접 주입한 경우) 그대로 사용
16+
if (config.headers.Authorization) return config;
17+
1518
let token = "";
1619

1720
// 1. sessionStorage에서 SSO 토큰(자체 JWT) 먼저 확인
1821
if (typeof window !== "undefined") {
1922
token = window.sessionStorage.getItem("sso_token") || "";
2023
}
2124

22-
// 2. SSO 토큰이 없다면 Firebase Auth에서 토큰 가져오기 시도
23-
if (!token) {
24-
const user = auth.currentUser;
25-
if (user) {
26-
token = await user.getIdToken();
27-
}
25+
if (!token && auth.currentUser) {
26+
token = await auth.currentUser.getIdToken();
2827
}
2928

3029
if (token) {

‎packages/ui/src/auth/auth-context.tsx‎

Lines changed: 76 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -82,64 +82,71 @@ export function AuthProvider({
8282

8383
const isRefreshing = useRef(false); // 중복 호출 방지용 Lock
8484

85-
const refreshSession = useCallback(async () => {
86-
if (isLoggingOut || isRefreshing.current) return false;
85+
const refreshSession = useCallback(
86+
async (manualToken?: string) => {
87+
if (isLoggingOut || isRefreshing.current) return false;
8788

88-
// 쿨타임 체크 추가 (함수 진입 시점에서도 보호)
89-
const now = Date.now();
90-
if (now - lastCheckTime.current < 2000) return false;
89+
// 쿨타임 체크 추가 (함수 진입 시점에서도 보호)
90+
const now = Date.now();
91+
if (now - lastCheckTime.current < 2000) return false;
9192

92-
isRefreshing.current = true; // 실행 시작 시 잠금
93-
lastCheckTime.current = now;
93+
isRefreshing.current = true; // 실행 시작 시 잠금
94+
lastCheckTime.current = now;
9495

95-
// 만약 글로벌 로그아웃 신호가 있다면 백엔드에 묻지 않고 바로 로컬 파기
96-
if (hasGlobalLogoutSignal()) {
97-
await clearLocalAuth();
98-
isInitialCheckDone.current = true;
99-
setLoading(false);
100-
return false;
101-
}
102-
103-
try {
104-
const response = await apiClient.get("/auth/users/me");
105-
if (response.data?.data) {
106-
const backendUser = response.data.data;
107-
const newUid = backendUser.id || backendUser.uid;
108-
109-
setUser((prev: any) => {
110-
// 필요한 핵심 필드만 비교
111-
const isSameUser =
112-
prev &&
113-
prev.uid === newUid &&
114-
prev.email === backendUser.email &&
115-
prev.photoURL ===
116-
(backendUser.photoURL || backendUser.profile_image);
117-
118-
if (isSameUser) return prev; // 데이터가 같으면 '절대' 새 객체를 만들지 않음
119-
120-
// 꼭 필요한 데이터만 정제해서 저장 (서버의 가변 필드 제외)
121-
return {
122-
uid: newUid,
123-
email: backendUser.email,
124-
displayName: backendUser.name || backendUser.displayName,
125-
photoURL: backendUser.photoURL || backendUser.profile_image,
126-
};
127-
});
128-
return true;
96+
// 만약 글로벌 로그아웃 신호가 있다면 백엔드에 묻지 않고 바로 로컬 파기
97+
if (hasGlobalLogoutSignal()) {
98+
await clearLocalAuth();
99+
isInitialCheckDone.current = true;
100+
setLoading(false);
101+
return false;
129102
}
130-
throw new Error("No session");
131-
} catch (error: any) {
132-
if (error.response?.status === 401) {
133-
await clearLocalAuth(); // 확실한 세션 만료 시에만 파기
103+
104+
try {
105+
const config = manualToken
106+
? { headers: { Authorization: `Bearer ${manualToken}` } }
107+
: {};
108+
109+
const response = await apiClient.get("/auth/users/me", config);
110+
if (response.data?.data) {
111+
const backendUser = response.data.data;
112+
const newUid = backendUser.id || backendUser.uid;
113+
114+
setUser((prev: any) => {
115+
// 필요한 핵심 필드만 비교
116+
const isSameUser =
117+
prev &&
118+
prev.uid === newUid &&
119+
prev.email === backendUser.email &&
120+
prev.photoURL ===
121+
(backendUser.photoURL || backendUser.profile_image);
122+
123+
if (isSameUser) return prev; // 데이터가 같으면 '절대' 새 객체를 만들지 않음
124+
125+
// 꼭 필요한 데이터만 정제해서 저장 (서버의 가변 필드 제외)
126+
return {
127+
uid: newUid,
128+
email: backendUser.email,
129+
displayName: backendUser.name || backendUser.displayName,
130+
photoURL: backendUser.photoURL || backendUser.profile_image,
131+
};
132+
});
133+
return true;
134+
}
135+
throw new Error("No session");
136+
} catch (error: any) {
137+
if (error.response?.status === 401) {
138+
await clearLocalAuth(); // 확실한 세션 만료 시에만 파기
139+
}
140+
// 그 외의 에러(500 등)는 세션을 유지하며 false만 반환
141+
return false;
142+
} finally {
143+
isInitialCheckDone.current = true;
144+
setLoading(false);
145+
isRefreshing.current = false; // 실행 완료 후 잠금 해제
134146
}
135-
// 그 외의 에러(500 등)는 세션을 유지하며 false만 반환
136-
return false;
137-
} finally {
138-
isInitialCheckDone.current = true;
139-
setLoading(false);
140-
isRefreshing.current = false; // 실행 완료 후 잠금 해제
141-
}
142-
}, [clearLocalAuth, isLoggingOut, hasGlobalLogoutSignal]);
147+
},
148+
[clearLocalAuth, isLoggingOut, hasGlobalLogoutSignal],
149+
);
143150

144151
useEffect(() => {
145152
const initAuth = async () => {
@@ -164,15 +171,20 @@ export function AuthProvider({
164171
if (ssoToken) {
165172
clearGlobalLogoutSignal();
166173
window.sessionStorage.setItem("sso_token", ssoToken);
167-
const newUrl =
168-
window.location.pathname +
169-
window.location.search
170-
.replace(/[?&]sso_token=[^&]+/, "")
171-
.replace(/^&/, "?")
172-
.replace(/\?$/, "");
173-
window.history.replaceState({}, "", newUrl);
174-
// SSO 토큰이 새로 들어왔을 때는 세션 갱신 필요
175-
await refreshSession();
174+
175+
// 1. 먼저 세션을 확실히 갱신
176+
const success = await refreshSession(ssoToken);
177+
178+
// 2. 세션 갱신에 성공했을 때만 URL을 청소
179+
if (success) {
180+
const newUrl =
181+
window.location.pathname +
182+
window.location.search
183+
.replace(/[?&]sso_token=[^&]+/, "")
184+
.replace(/^&/, "?")
185+
.replace(/\?$/, "");
186+
window.history.replaceState({}, "", newUrl);
187+
}
176188
return;
177189
}
178190

@@ -240,7 +252,8 @@ export function AuthProvider({
240252
isInitialCheckDone.current &&
241253
!user &&
242254
requireAuth &&
243-
!isLoggingOut
255+
!isLoggingOut &&
256+
!isRefreshing.current
244257
) {
245258
if (typeof window !== "undefined") {
246259
if (window.location.pathname.startsWith("/login")) return;

0 commit comments

Comments
 (0)