From 7738cd11087d00dd81e53bc545fe5c7dced7a468 Mon Sep 17 00:00:00 2001 From: GSB0203 Date: Tue, 15 Jul 2025 14:50:54 +0900 Subject: [PATCH 1/8] =?UTF-8?q?fix:=20HttpOnly=20=EC=BF=A0=ED=82=A4=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EC=9D=B8=EC=A6=9D=20=EC=8B=9C=EC=8A=A4?= =?UTF-8?q?=ED=85=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HttpOnly 쿠키를 사용하면서도 프론트엔드에서 인증 처리 - 로컬스토리지에 인증 상태만 저장 (토큰은 HttpOnly 쿠키에 보관) - 백엔드 API 호출 성공 여부로 인증 판단 - authenticatedApiRequest에서 credentials: 'include' 사용 - 24시간 인증 만료 처리 추가 --- app/auth/callback/page.tsx | 33 ++-- app/components/ProtectedRoute.tsx | 2 + app/contexts/AuthContext.tsx | 13 +- app/home/page.tsx | 243 +++++++++++++++--------------- app/settings/page.tsx | 2 +- app/utils/api.ts | 96 ++++++++++-- 6 files changed, 222 insertions(+), 167 deletions(-) diff --git a/app/auth/callback/page.tsx b/app/auth/callback/page.tsx index f7a2a99..8ee925e 100644 --- a/app/auth/callback/page.tsx +++ b/app/auth/callback/page.tsx @@ -62,28 +62,17 @@ function AuthCallbackContent() { const data = await response.json(); if (data.success) { - // 백엔드에서 쿠키로 토큰을 설정했는지 확인 - const accessToken = getCookie('access_social'); - const refreshToken = getCookie('refresh_social'); - - console.log('=== Cookie Check After API Call ==='); - console.log('Access Token from cookie:', accessToken ? 'Found' : 'Not found'); - console.log('Refresh Token from cookie:', refreshToken ? 'Found' : 'Not found'); - - if (accessToken && refreshToken) { - console.log('✅ JWT tokens found in cookies - authentication successful'); - setTokens(accessToken, refreshToken); - setStatus('success'); - setMessage('로그인 성공! 홈으로 이동합니다.'); - notifyAuthStateChange(); - setTimeout(() => { - router.push('/home'); - }, 1500); - } else { - console.log('❌ No JWT tokens in cookies after API call'); - setStatus('error'); - setMessage('인증 토큰을 받지 못했습니다. 다시 시도해주세요.'); - } + console.log('✅ Backend API call successful - authentication successful'); + + localStorage.setItem('isAuthenticated', 'true'); + localStorage.setItem('authTimestamp', Date.now().toString()); + + setStatus('success'); + setMessage('로그인 성공! 홈으로 이동합니다.'); + notifyAuthStateChange(); + setTimeout(() => { + router.push('/home'); + }, 1500); } else { throw new Error(data.message || '로그인 처리 중 오류가 발생했습니다.'); } diff --git a/app/components/ProtectedRoute.tsx b/app/components/ProtectedRoute.tsx index ddb9a17..52fae44 100644 --- a/app/components/ProtectedRoute.tsx +++ b/app/components/ProtectedRoute.tsx @@ -14,7 +14,9 @@ export default function ProtectedRoute({ children, fallback }: ProtectedRoutePro const router = useRouter(); useEffect(() => { + console.log('ProtectedRoute - Auth state:', { isLoggedIn, loading }); if (!loading && !isLoggedIn) { + console.log('ProtectedRoute - Redirecting to / due to not logged in'); router.push('/'); } }, [isLoggedIn, loading, router]); diff --git a/app/contexts/AuthContext.tsx b/app/contexts/AuthContext.tsx index 008af78..cb27353 100644 --- a/app/contexts/AuthContext.tsx +++ b/app/contexts/AuthContext.tsx @@ -29,13 +29,14 @@ export const AuthProvider: React.FC = ({ children }) => { const [loading, setLoading] = useState(true); const checkAuth = () => { - // JWT 토큰만 확인 (세션 기반 인증 제거) - const token = getAccessToken(); - const newLoginState = !!token; + console.log('=== AuthContext checkAuth ==='); + console.log('Document available:', typeof document !== 'undefined'); + + const newLoginState = isAuthenticated(); + console.log('Setting isLoggedIn to:', newLoginState); + setIsLoggedIn(newLoginState); setLoading(false); - - console.log('Auth check:', { token: !!token, isLoggedIn: newLoginState }); }; const logout = () => { @@ -54,10 +55,8 @@ export const AuthProvider: React.FC = ({ children }) => { checkAuth(); }; - // storage 이벤트 리스너 (다른 탭에서의 변경 감지) window.addEventListener('storage', handleStorageChange); - // 현재 탭에서의 변경 감지를 위한 커스텀 이벤트 window.addEventListener('authStateChanged', handleStorageChange); return () => { diff --git a/app/home/page.tsx b/app/home/page.tsx index 4598a31..30cc115 100644 --- a/app/home/page.tsx +++ b/app/home/page.tsx @@ -4,7 +4,6 @@ import { useState, useEffect } from "react" import { useRouter } from "next/navigation" import Container from "../components/Container" import NavigationBar from "../components/NavigationBar" -import ProtectedRoute from "../components/ProtectedRoute" const EMOTION_COLORS = { 즐거움: '#3DC8EF', @@ -209,151 +208,149 @@ export default function Register() { ]; return ( - - -
-
- {childName} + +
+
+ {childName} +
+ +
+
+ +

+ {currentMonth.getFullYear()}년 {currentMonth.getMonth() + 1}월 +

+
-
-
- -

- {currentMonth.getFullYear()}년 {currentMonth.getMonth() + 1}월 -

- -
+
+ {['일', '월', '화', '수', '목', '금', '토'].map(day => ( +
+ {day} +
+ ))} +
-
- {['일', '월', '화', '수', '목', '금', '토'].map(day => ( -
- {day} -
- ))} -
+
+ {calendarDays.map(date => ( + + ))} +
+
-
- {calendarDays.map(date => ( + {selectedDate && selectedDiary && ( +
+
+ {timeSlots.map((timeSlot) => ( ))}
+ )} - {selectedDate && selectedDiary && ( -
-
- {timeSlots.map((timeSlot) => ( - - ))} -
+ {selectedDate && selectedDiary && ( +
+
+ {new Date(selectedDate).toLocaleDateString('ko-KR', { + year: 'numeric', + month: 'long', + day: 'numeric', + weekday: 'long' + })}
- )} - {selectedDate && selectedDiary && ( -
-
- {new Date(selectedDate).toLocaleDateString('ko-KR', { - year: 'numeric', - month: 'long', - day: 'numeric', - weekday: 'long' - })} +
+
+ {timeSlots.find(t => t.key === selectedTimeSlot)?.label} 기록
- -
-
- {timeSlots.find(t => t.key === selectedTimeSlot)?.label} 기록 -
+ + {(() => { + const timeData = selectedDiary[selectedTimeSlot]; - {(() => { - const timeData = selectedDiary[selectedTimeSlot]; - - return ( -
-
-
- - {timeData.predictedEmotions.map(e => e.emotion).join(', ')} - -
-

- {timeData.predictedText} -

+ return ( +
+
+
+ + {timeData.predictedEmotions.map(e => e.emotion).join(', ')} +
+

+ {timeData.predictedText} +

+
-
-
- - {timeData.actualEmotions.map(e => e.emotion).join(', ')} - -
-

- {timeData.actualText} -

+
+
+ + {timeData.actualEmotions.map(e => e.emotion).join(', ')} +
+

+ {timeData.actualText} +

- ); - })()} -
+
+ ); + })()}
- )} +
+ )} - {!selectedDate && ( -
-
- - - -
-

- 과거 날짜를 클릭해서 {childName}의 감정 일기를 확인해보세요 -

-

- 예측과 실제 감정을 비교하여 분석해드립니다 -

+ {!selectedDate && ( +
+
+ + +
- )} -
- - - - +

+ 과거 날짜를 클릭해서 {childName}의 감정 일기를 확인해보세요 +

+

+ 예측과 실제 감정을 비교하여 분석해드립니다 +

+
+ )} +
+ + + ) } \ No newline at end of file diff --git a/app/settings/page.tsx b/app/settings/page.tsx index 6b650d8..95523ee 100644 --- a/app/settings/page.tsx +++ b/app/settings/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState } from "react" +import { useState, useEffect } from "react" import { useRouter } from "next/navigation" import Container from "../components/Container" import NavigationBar from "../components/NavigationBar" diff --git a/app/utils/api.ts b/app/utils/api.ts index 50a8ce1..f396c80 100644 --- a/app/utils/api.ts +++ b/app/utils/api.ts @@ -21,37 +21,106 @@ export const getCookie = (name: string): string | null => { return null; }; -// 토큰 관리 함수들 - 백엔드 쿠키 이름 그대로 사용 +// 토큰 관리 함수들 - HttpOnly 쿠키 사용으로 인증 상태만 확인 export const getAccessToken = (): string | null => { if (typeof window === 'undefined') return null; - // 백엔드에서 설정한 쿠키 이름 그대로 사용 - return getCookie('access_social'); + // HttpOnly 쿠키는 JavaScript에서 읽을 수 없으므로 + // 로컬스토리지의 인증 상태만 확인 + const isAuthenticated = localStorage.getItem('isAuthenticated') === 'true'; + const authTimestamp = localStorage.getItem('authTimestamp'); + + if (!isAuthenticated || !authTimestamp) { + return null; + } + + // 인증 상태가 24시간 이내인지 확인 + const authAge = Date.now() - parseInt(authTimestamp); + const isRecentAuth = authAge < 24 * 60 * 60 * 1000; // 24시간 + + if (!isRecentAuth) { + // 만료된 인증 상태 삭제 + clearTokens(); + return null; + } + + // HttpOnly 쿠키는 백엔드에서만 읽을 수 있으므로 + // 인증 상태만 반환 (실제 토큰은 백엔드에서 처리) + return 'authenticated'; }; export const getRefreshToken = (): string | null => { if (typeof window === 'undefined') return null; - // 백엔드에서 설정한 쿠키 이름 그대로 사용 - return getCookie('refresh_social'); + // HttpOnly 쿠키는 JavaScript에서 읽을 수 없으므로 + // 로컬스토리지의 인증 상태만 확인 + const isAuthenticated = localStorage.getItem('isAuthenticated') === 'true'; + const authTimestamp = localStorage.getItem('authTimestamp'); + + if (!isAuthenticated || !authTimestamp) { + return null; + } + + // 인증 상태가 24시간 이내인지 확인 + const authAge = Date.now() - parseInt(authTimestamp); + const isRecentAuth = authAge < 24 * 60 * 60 * 1000; // 24시간 + + if (!isRecentAuth) { + // 만료된 인증 상태 삭제 + clearTokens(); + return null; + } + + // HttpOnly 쿠키는 백엔드에서만 읽을 수 있으므로 + // 인증 상태만 반환 (실제 토큰은 백엔드에서 처리) + return 'authenticated'; }; -// 토큰 설정 함수 - 백엔드에서 쿠키로 관리하므로 빈 함수 +// 토큰 설정 함수 - HttpOnly 쿠키 사용으로 인증 상태만 저장 export const setTokens = (accessToken: string, refreshToken?: string) => { - // 백엔드에서 쿠키로 설정하므로 프론트엔드에서는 설정하지 않음 - console.log('Tokens are managed by backend cookies'); + if (typeof window === 'undefined') return; + + // HttpOnly 쿠키는 백엔드에서 설정하므로 + // 프론트엔드에서는 인증 상태만 저장 + localStorage.setItem('isAuthenticated', 'true'); + localStorage.setItem('authTimestamp', Date.now().toString()); + + console.log('Authentication state saved to localStorage'); }; export const clearTokens = () => { if (typeof window === 'undefined') return; - // 백엔드 쿠키 이름 그대로 사용하여 삭제 + // 백엔드 쿠키 삭제 (가능한 경우) document.cookie = 'access_social=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; document.cookie = 'refresh_social=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; + + // 로컬스토리지의 인증 상태 삭제 + localStorage.removeItem('isAuthenticated'); + localStorage.removeItem('authTimestamp'); }; export const isAuthenticated = (): boolean => { - return getAccessToken() !== null; + if (typeof window === 'undefined') return false; + + const isAuthInStorage = localStorage.getItem('isAuthenticated') === 'true'; + const authTimestamp = localStorage.getItem('authTimestamp'); + + if (!isAuthInStorage || !authTimestamp) { + return false; + } + + // 인증 상태가 24시간 이내인지 확인 + const authAge = Date.now() - parseInt(authTimestamp); + const isRecentAuth = authAge < 24 * 60 * 60 * 1000; // 24시간 + + if (!isRecentAuth) { + // 만료된 인증 상태 삭제 + clearTokens(); + return false; + } + + return true; }; // API 응답 타입 정의 @@ -68,8 +137,8 @@ export const authenticatedApiRequest = async ( options: RequestInit = {} ): Promise> => { try { - const accessToken = getAccessToken(); - if (!accessToken) { + // HttpOnly 쿠키를 사용하므로 인증 상태만 확인 + if (!isAuthenticated()) { throw new Error('인증 토큰이 없습니다.'); } @@ -77,10 +146,9 @@ export const authenticatedApiRequest = async ( const response = await fetch(url, { headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, ...options.headers, }, - credentials: 'include', // 쿠키 포함 + credentials: 'include', // HttpOnly 쿠키 자동 포함 ...options, }); From 0674a8dfb10048a162ce698dcbb615a4a087b28c Mon Sep 17 00:00:00 2001 From: GSB0203 Date: Tue, 15 Jul 2025 15:00:06 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat=20::=20=EB=A1=9C=EC=BB=AC=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=20log=20n=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/auth/callback/page.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/auth/callback/page.tsx b/app/auth/callback/page.tsx index 8ee925e..6caea8d 100644 --- a/app/auth/callback/page.tsx +++ b/app/auth/callback/page.tsx @@ -63,10 +63,20 @@ function AuthCallbackContent() { if (data.success) { console.log('✅ Backend API call successful - authentication successful'); + console.log('Response data:', data); + + // 로컬스토리지 저장 전 확인 + console.log('=== LocalStorage Save Debug ==='); + console.log('Before saving - localStorage:', localStorage.getItem('isAuthenticated')); localStorage.setItem('isAuthenticated', 'true'); localStorage.setItem('authTimestamp', Date.now().toString()); + // 로컬스토리지 저장 후 확인 + console.log('After saving - isAuthenticated:', localStorage.getItem('isAuthenticated')); + console.log('After saving - authTimestamp:', localStorage.getItem('authTimestamp')); + console.log('All localStorage keys:', Object.keys(localStorage)); + setStatus('success'); setMessage('로그인 성공! 홈으로 이동합니다.'); notifyAuthStateChange(); From a10ac6c11f057ad9a473571293bef272c185c6db Mon Sep 17 00:00:00 2001 From: GSB0203 Date: Tue, 15 Jul 2025 15:09:38 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat=20::=20=EB=A1=9C=EC=BB=AC=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/auth/callback/page.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/auth/callback/page.tsx b/app/auth/callback/page.tsx index 6caea8d..f895b52 100644 --- a/app/auth/callback/page.tsx +++ b/app/auth/callback/page.tsx @@ -77,9 +77,11 @@ function AuthCallbackContent() { console.log('After saving - authTimestamp:', localStorage.getItem('authTimestamp')); console.log('All localStorage keys:', Object.keys(localStorage)); + // AuthContext 강제 업데이트 + notifyAuthStateChange(); + setStatus('success'); setMessage('로그인 성공! 홈으로 이동합니다.'); - notifyAuthStateChange(); setTimeout(() => { router.push('/home'); }, 1500); From 3a5cc56759220cecb80f90f87b7e36c20d5f8d26 Mon Sep 17 00:00:00 2001 From: GSB0203 Date: Tue, 15 Jul 2025 16:28:34 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat=20::=20=EB=A1=9C=EC=BB=AC=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/auth/callback/page.tsx | 2 +- app/contexts/AuthContext.tsx | 8 ++++++++ app/utils/api.ts | 12 ------------ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/app/auth/callback/page.tsx b/app/auth/callback/page.tsx index f895b52..2304690 100644 --- a/app/auth/callback/page.tsx +++ b/app/auth/callback/page.tsx @@ -2,7 +2,7 @@ import { useEffect, useState, Suspense } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; -import { API_BASE_URL, getCookie, setTokens } from '../../utils/api'; +import { API_BASE_URL, setTokens } from '../../utils/api'; function AuthCallbackContent() { const router = useRouter(); diff --git a/app/contexts/AuthContext.tsx b/app/contexts/AuthContext.tsx index cb27353..60705da 100644 --- a/app/contexts/AuthContext.tsx +++ b/app/contexts/AuthContext.tsx @@ -32,7 +32,15 @@ export const AuthProvider: React.FC = ({ children }) => { console.log('=== AuthContext checkAuth ==='); console.log('Document available:', typeof document !== 'undefined'); + // isAuthenticated 함수 사용 (로컬스토리지 기반) const newLoginState = isAuthenticated(); + + // 디버깅을 위한 추가 로그 + console.log('isAuthenticated() result:', newLoginState); + console.log('localStorage isAuthenticated:', localStorage.getItem('isAuthenticated')); + console.log('localStorage authTimestamp:', localStorage.getItem('authTimestamp')); + console.log('All localStorage keys:', Object.keys(localStorage)); + console.log('Setting isLoggedIn to:', newLoginState); setIsLoggedIn(newLoginState); diff --git a/app/utils/api.ts b/app/utils/api.ts index f396c80..5a53243 100644 --- a/app/utils/api.ts +++ b/app/utils/api.ts @@ -9,18 +9,6 @@ export const handleKakaoLogin = () => { window.location.href = KAKAO_LOGIN_URL!; }; -// 쿠키에서 토큰 읽기 함수 -export const getCookie = (name: string): string | null => { - if (typeof document === 'undefined') return null; - - const value = `; ${document.cookie}`; - const parts = value.split(`; ${name}=`); - if (parts.length === 2) { - return parts.pop()?.split(';').shift() || null; - } - return null; -}; - // 토큰 관리 함수들 - HttpOnly 쿠키 사용으로 인증 상태만 확인 export const getAccessToken = (): string | null => { if (typeof window === 'undefined') return null; From cbcbdb4bff04201c693cad561fef0b34887f81fc Mon Sep 17 00:00:00 2001 From: GSB0203 Date: Tue, 15 Jul 2025 16:32:55 +0900 Subject: [PATCH 5/8] =?UTF-8?q?fix:=20AuthContext=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=A1=9C=EC=BB=AC=EC=8A=A4=ED=86=A0=EB=A6=AC=EC=A7=80=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20=EA=B0=90?= =?UTF-8?q?=EC=A7=80=20=EB=B0=8F=20=EC=9D=B8=EC=A6=9D=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=20=EB=8F=99=EA=B8=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - storage, authStateChanged 이벤트 및 polling으로 인증 상태 동기화 - 로그인 후 인증 상태가 즉시 반영되도록 개선 --- app/contexts/AuthContext.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/contexts/AuthContext.tsx b/app/contexts/AuthContext.tsx index 60705da..da270dc 100644 --- a/app/contexts/AuthContext.tsx +++ b/app/contexts/AuthContext.tsx @@ -60,18 +60,31 @@ export const AuthProvider: React.FC = ({ children }) => { // localStorage 변경 감지 useEffect(() => { const handleStorageChange = () => { + console.log('=== Storage change detected ==='); checkAuth(); }; + // storage 이벤트 리스너 (다른 탭에서의 변경 감지) window.addEventListener('storage', handleStorageChange); + // 현재 탭에서의 변경 감지를 위한 커스텀 이벤트 window.addEventListener('authStateChanged', handleStorageChange); + + // 주기적으로 인증 상태 확인 (개발용) + const interval = setInterval(() => { + const currentAuth = isAuthenticated(); + if (currentAuth !== isLoggedIn) { + console.log('Auth state changed from', isLoggedIn, 'to', currentAuth); + checkAuth(); + } + }, 1000); // 1초마다 확인 return () => { window.removeEventListener('storage', handleStorageChange); window.removeEventListener('authStateChanged', handleStorageChange); + clearInterval(interval); }; - }, []); + }, [isLoggedIn]); const value: AuthContextType = { isLoggedIn, From 266228e50a09ba402e13149893192064069309e9 Mon Sep 17 00:00:00 2001 From: GSB0203 Date: Tue, 15 Jul 2025 16:40:13 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat::=20=EB=A1=9C=EC=BB=AC=EC=8A=A4?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=EC=A7=80=20=EA=B0=95=EC=A0=9C=20=EC=83=88?= =?UTF-8?q?=EB=A1=9C=EA=B3=A0=EC=B9=A8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/auth/callback/page.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/auth/callback/page.tsx b/app/auth/callback/page.tsx index 2304690..1c8cbfd 100644 --- a/app/auth/callback/page.tsx +++ b/app/auth/callback/page.tsx @@ -71,6 +71,8 @@ function AuthCallbackContent() { localStorage.setItem('isAuthenticated', 'true'); localStorage.setItem('authTimestamp', Date.now().toString()); + window.dispatchEvent(new Event('authStateChanged')); + window.location.reload(); // 강제 새로고침 // 로컬스토리지 저장 후 확인 console.log('After saving - isAuthenticated:', localStorage.getItem('isAuthenticated')); From 1244565745dfeb5006820fd509759ec253be6903 Mon Sep 17 00:00:00 2001 From: GSB0203 Date: Tue, 15 Jul 2025 17:16:44 +0900 Subject: [PATCH 7/8] =?UTF-8?q?fix:=20=EB=B0=B1=EC=97=94=EB=93=9C=20API=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=20=EC=9D=B8=EC=A6=9D=20=EC=83=81=ED=83=9C=20?= =?UTF-8?q?=ED=99=95=EC=9D=B8=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AuthContext에서 /api/auth/check 엔드포인트 호출로 인증 상태 확인 - HttpOnly 쿠키 기반 인증 + localStorage UI 상태 관리 - 백엔드 응답에 따라 localStorage 자동 업데이트 - 로그인 콜백에서 서버 응답 디버깅 로그 추가 --- app/auth/callback/page.tsx | 12 +++++---- app/contexts/AuthContext.tsx | 49 +++++++++++++++++++++++++++--------- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/app/auth/callback/page.tsx b/app/auth/callback/page.tsx index 1c8cbfd..a5a79b3 100644 --- a/app/auth/callback/page.tsx +++ b/app/auth/callback/page.tsx @@ -55,11 +55,17 @@ function AuthCallbackContent() { }), }); + console.log('=== Backend Response Debug ==='); + console.log('Response status:', response.status); + console.log('Response ok:', response.ok); + console.log('Response headers:', Object.fromEntries(response.headers.entries())); + if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); + console.log('Response data:', data); if (data.success) { console.log('✅ Backend API call successful - authentication successful'); @@ -71,19 +77,15 @@ function AuthCallbackContent() { localStorage.setItem('isAuthenticated', 'true'); localStorage.setItem('authTimestamp', Date.now().toString()); - window.dispatchEvent(new Event('authStateChanged')); - window.location.reload(); // 강제 새로고침 // 로컬스토리지 저장 후 확인 console.log('After saving - isAuthenticated:', localStorage.getItem('isAuthenticated')); console.log('After saving - authTimestamp:', localStorage.getItem('authTimestamp')); console.log('All localStorage keys:', Object.keys(localStorage)); - // AuthContext 강제 업데이트 - notifyAuthStateChange(); - setStatus('success'); setMessage('로그인 성공! 홈으로 이동합니다.'); + notifyAuthStateChange(); setTimeout(() => { router.push('/home'); }, 1500); diff --git a/app/contexts/AuthContext.tsx b/app/contexts/AuthContext.tsx index da270dc..d19558a 100644 --- a/app/contexts/AuthContext.tsx +++ b/app/contexts/AuthContext.tsx @@ -32,19 +32,44 @@ export const AuthProvider: React.FC = ({ children }) => { console.log('=== AuthContext checkAuth ==='); console.log('Document available:', typeof document !== 'undefined'); - // isAuthenticated 함수 사용 (로컬스토리지 기반) - const newLoginState = isAuthenticated(); - - // 디버깅을 위한 추가 로그 - console.log('isAuthenticated() result:', newLoginState); - console.log('localStorage isAuthenticated:', localStorage.getItem('isAuthenticated')); - console.log('localStorage authTimestamp:', localStorage.getItem('authTimestamp')); - console.log('All localStorage keys:', Object.keys(localStorage)); - - console.log('Setting isLoggedIn to:', newLoginState); + // 백엔드 API를 호출해서 인증 상태 확인 + const verifyAuth = async () => { + try { + console.log('Checking auth with backend...'); + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/auth/check`, { + method: 'GET', + credentials: 'include', // HttpOnly 쿠키 포함 + }); + + console.log('Backend auth check response:', response.status, response.ok); + + if (response.ok) { + // 백엔드에서 인증 성공 + localStorage.setItem('isAuthenticated', 'true'); + localStorage.setItem('authTimestamp', Date.now().toString()); + console.log('✅ Backend auth successful - setting localStorage'); + } else { + // 백엔드에서 인증 실패 + localStorage.removeItem('isAuthenticated'); + localStorage.removeItem('authTimestamp'); + console.log('❌ Backend auth failed - clearing localStorage'); + } + + const newLoginState = response.ok; + console.log('Setting isLoggedIn to:', newLoginState); + + setIsLoggedIn(newLoginState); + setLoading(false); + } catch (error) { + console.error('Auth verification failed:', error); + localStorage.removeItem('isAuthenticated'); + localStorage.removeItem('authTimestamp'); + setIsLoggedIn(false); + setLoading(false); + } + }; - setIsLoggedIn(newLoginState); - setLoading(false); + verifyAuth(); }; const logout = () => { From 7942697870536fee6c397c3794dd46129639fcd2 Mon Sep 17 00:00:00 2001 From: GSB0203 Date: Tue, 15 Jul 2025 17:22:54 +0900 Subject: [PATCH 8/8] =?UTF-8?q?chore:=20API=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9D=B8=EC=A6=9D=20=EC=83=81=ED=83=9C=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20=EB=A1=9C=EA=B9=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/contexts/AuthContext.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/contexts/AuthContext.tsx b/app/contexts/AuthContext.tsx index d19558a..fa3d699 100644 --- a/app/contexts/AuthContext.tsx +++ b/app/contexts/AuthContext.tsx @@ -36,13 +36,14 @@ export const AuthProvider: React.FC = ({ children }) => { const verifyAuth = async () => { try { console.log('Checking auth with backend...'); + console.log('API_BASE_URL:', process.env.NEXT_PUBLIC_API_BASE_URL); + console.log('Full URL:', `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/auth/check`); + const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/auth/check`, { method: 'GET', credentials: 'include', // HttpOnly 쿠키 포함 }); - console.log('Backend auth check response:', response.status, response.ok); - if (response.ok) { // 백엔드에서 인증 성공 localStorage.setItem('isAuthenticated', 'true');