diff --git a/apps/web/src/app/mypage/_actions/logout.ts b/apps/web/src/app/mypage/_actions/logout.ts index 991c4c38..c7108f78 100644 --- a/apps/web/src/app/mypage/_actions/logout.ts +++ b/apps/web/src/app/mypage/_actions/logout.ts @@ -4,10 +4,9 @@ import { cookies } from 'next/headers'; import { deleteFcmToken } from '@/apis/deleteFcmToken'; import { serverApi } from '@/apis/server'; +import { clearAuthCookies } from '@/app/mypage/_common/_actions/clearAuthCookies'; import { ENDPOINTS } from '@/consts/api'; -const AUTH_COOKIE_NAMES = ['access_token', 'refresh_token', 'device_id'] as const; - export const logout = async () => { const cookieStore = await cookies(); const deviceId = cookieStore.get('device_id')?.value; @@ -26,7 +25,5 @@ export const logout = async () => { // 백엔드 실패해도 로컬 쿠키는 삭제 } - AUTH_COOKIE_NAMES.forEach(cookieName => { - if (cookieStore.has(cookieName)) cookieStore.delete(cookieName); - }); + await clearAuthCookies(); }; diff --git a/apps/web/src/app/mypage/_common/_actions/clearAuthCookies.ts b/apps/web/src/app/mypage/_common/_actions/clearAuthCookies.ts new file mode 100644 index 00000000..623f71f6 --- /dev/null +++ b/apps/web/src/app/mypage/_common/_actions/clearAuthCookies.ts @@ -0,0 +1,14 @@ +'use server'; + +import { cookies } from 'next/headers'; + +const AUTH_COOKIE_NAMES = ['access_token', 'refresh_token', 'device_id'] as const; + +/** 브라우저 httpOnly 인증 쿠키는 JS 로 지울 수 없어 서버 액션으로 폐기한다 */ +export const clearAuthCookies = async () => { + const cookieStore = await cookies(); + + AUTH_COOKIE_NAMES.forEach(cookieName => { + if (cookieStore.has(cookieName)) cookieStore.delete(cookieName); + }); +}; diff --git a/apps/web/src/app/mypage/withdraw/_hooks/useDeleteMe.ts b/apps/web/src/app/mypage/withdraw/_hooks/useDeleteMe.ts index 1d1f6c5b..0714f367 100644 --- a/apps/web/src/app/mypage/withdraw/_hooks/useDeleteMe.ts +++ b/apps/web/src/app/mypage/withdraw/_hooks/useDeleteMe.ts @@ -1,8 +1,10 @@ import { WEBBRIDGE_MESSAGE_TYPE } from '@piki/core'; +import * as Sentry from '@sentry/nextjs'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useRouter } from 'next/navigation'; import { toast } from 'sonner'; +import { clearAuthCookies } from '@/app/mypage/_common/_actions/clearAuthCookies'; import { ROUTES } from '@/consts/route'; import { isGlobalNetError } from '@/utils/apiError'; import { deleteCookie } from '@/utils/cookie'; @@ -17,14 +19,19 @@ export const useDeleteMe = () => { const { mutate: deleteMeMutation, isPending: isDeleteMePending } = useMutation({ mutationFn: deleteMe, - onSuccess: () => { + onSuccess: async () => { if (isWebview()) { deleteCookie('access_token'); deleteCookie('refresh_token'); WebBridge.postMessage({ type: WEBBRIDGE_MESSAGE_TYPE.WEB_REQ_LOGOUT }); + } else { + /** 웹인 경우 쿠키 삭제 */ + await clearAuthCookies(); } + Sentry.setUser(null); + queryClient.clear(); router.replace(ROUTES.ROOT); }, diff --git a/apps/web/src/proxy.ts b/apps/web/src/proxy.ts index 11f13070..6b308509 100644 --- a/apps/web/src/proxy.ts +++ b/apps/web/src/proxy.ts @@ -217,8 +217,8 @@ const handleTokenRefresh = async (request: NextRequest) => { } }; -/** 세션 만료 신호로 진입한 로그인 - 토큰 폐기 후 페이지 진입 */ -const handleSessionExpired = (request: NextRequest) => { +/** 세션 만료·탈퇴 신호로 진입한 로그인 - 토큰 폐기 후 페이지 진입 */ +const handleAuthReset = (request: NextRequest) => { /** 페이지 렌더링 시 죽은 토큰을 보지 않도록 남은 쿠키에서 삭제 */ const remainingCookies = request.cookies .getAll() @@ -255,10 +255,12 @@ export const proxy = async (request: NextRequest) => { /** 퍼블릭 영역 */ if (routeType === 'PUBLIC') { - const isSessionExpired = - pathname === ROUTES.LOGIN && - request.nextUrl.searchParams.get(QUERY_ACTION.KEY) === QUERY_ACTION.VALUE.SESSION_EXPIRED; - if (isSessionExpired) return handleSessionExpired(request); + const loginAction = + pathname === ROUTES.LOGIN ? request.nextUrl.searchParams.get(QUERY_ACTION.KEY) : null; + const isAuthReset = + loginAction === QUERY_ACTION.VALUE.SESSION_EXPIRED || + loginAction === QUERY_ACTION.VALUE.WITHDRAWN_ACCOUNT; + if (isAuthReset) return handleAuthReset(request); const needsRestore = pathname === ROUTES.LOGIN &&