-
Notifications
You must be signed in to change notification settings - Fork 2
Feat: 토큰 인증 시스템의 엣지 케이스 해결 및 레거시 코드 정리 #81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
이 PR은 HttpOnly Cookie 기반 인증 시스템의 엣지 케이스를 해결하고 레거시 코드를 정리하는 작업입니다. 주요 개선사항으로는 미들웨어의 순환 참조 문제 해결, 쿠키 도메인 속성 제거, SSR prefetch 캐싱 문제 해결, 그리고 더 이상 사용하지 않는 Zustand 기반 authStore 제거가 포함됩니다.
- 미들웨어 순환 참조 해결: 프록시 대신 백엔드 API를 직접 호출하여 SocketError 해결
- 쿠키 도메인 처리 개선: Set-Cookie 헤더의 Domain 속성을 제거하여 환경별 자동 설정
- SSR 401 엣지케이스 해결: 동적 staleTime을 사용하여 에러 시 즉시 재검증 가능
- 코드 정리: Zustand authStore 제거 및 React Query 기반 인증으로 완전 전환
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/web/src/stores/authStore.ts | Zustand 기반 인증 스토어 완전 제거 (59줄 삭제) |
| apps/web/src/middleware.ts | 백엔드 직접 호출 방식으로 변경, Domain 속성 제거 로직 추가 |
| apps/web/src/providers/AuthHydrationProvider.tsx | 동적 staleTime으로 SSR 401 에러 시 즉시 재검증 설정 |
| apps/web/src/components/buttons/Button.tsx | inline-flex에서 flex로 변경 및 Pressable 래퍼에 className 추가 |
| apps/web/src/app/page.tsx | 클라이언트 버튼을 별도 StartButton 컴포넌트로 분리 |
| apps/web/src/app/(auth)/components/StartButton.tsx | 루트 페이지용 시작 버튼 컴포넌트 신규 추가 |
| apps/web/src/app/main/profile/page.tsx | authStore 대신 useAuth 훅을 사용한 로그아웃 구현 |
| apps/web/src/app/(auth)/signup/layout.tsx | 중복 RedirectIfAuthed 컴포넌트 제거 (미들웨어에서 처리) |
| apps/web/src/app/(auth)/signup/components/RedirectIfAuthed.tsx | 미들웨어와 중복되는 클라이언트 리다이렉트 컴포넌트 삭제 (43줄) |
| apps/web/src/app/(auth)/signup/[type]/complete/page.tsx | authStore 대신 useAuth 훅 사용, User 타입으로 간소화 |
|
|
||
| const classes = cn( | ||
| 'relative inline-flex items-center justify-center rounded-lg font-medium select-none overflow-hidden', | ||
| 'relative flex items-center justify-center rounded-lg font-medium select-none overflow-hidden', |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inline-flex에서 flex로 변경하면 버튼이 블록 레벨 요소처럼 동작하여 전체 너비를 차지하게 됩니다. 이는 의도하지 않은 레이아웃 변경을 초래할 수 있습니다.
영향:
- 기존: 버튼이 콘텐츠 크기만큼만 차지 (inline-flex)
- 변경 후: 버튼이 부모 요소의 전체 너비를 차지할 수 있음 (flex)
className="w-full"을 명시적으로 사용하는 경우가 아니라면 inline-flex를 유지하는 것이 안전합니다.
제안:
'relative inline-flex items-center justify-center rounded-lg font-medium select-none overflow-hidden',| 'relative flex items-center justify-center rounded-lg font-medium select-none overflow-hidden', | |
| 'relative inline-flex items-center justify-center rounded-lg font-medium select-none overflow-hidden', |
| * - Domain 속성 제거 (현재 도메인으로 자동 설정) | ||
| */ | ||
| function modifySetCookie(cookie: string): string { | ||
| return cookie.replace(/Domain=[^;]+;?\s*/gi, ''); |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
정규식이 Set-Cookie 헤더의 첫 번째 속성이 Domain인 경우를 올바르게 처리하지 못합니다.
문제 시나리오:
Domain=example.com; Path=/; Secure
이 경우 Domain=[^;]+;?\s*는 Domain=example.com; 를 제거하여 Path=/; Secure를 반환합니다. 하지만 첫 번째 속성이므로 세미콜론이 남아있으면 문제가 됩니다.
일반적으로 Set-Cookie 헤더는 name=value로 시작하고 속성들이 세미콜론으로 구분되므로, Domain은 첫 번째가 아닌 중간 속성일 가능성이 높습니다.
더 안전한 패턴:
function modifySetCookie(cookie: string): string {
return cookie.replace(/;\s*Domain=[^;]+/gi, '');
}이렇게 하면 세미콜론을 포함하여 제거하므로 다른 속성과의 구분이 명확해집니다.
| return cookie.replace(/Domain=[^;]+;?\s*/gi, ''); | |
| // 중간/끝에 위치한 Domain 속성 제거 | |
| let result = cookie.replace(/;\s*Domain=[^;]+/gi, ''); | |
| // 맨 앞에 위치한 Domain 속성 제거 | |
| result = result.replace(/^Domain=[^;]+;?\s*/i, ''); | |
| return result; |
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| Cookie: `refreshToken=${refreshToken}`, |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
쿠키를 수동으로 구성할 때 refreshToken 값에 대한 URL 인코딩이 누락되어 있습니다. 토큰 값에 특수 문자가 포함된 경우 파싱 오류가 발생할 수 있습니다.
제안:
Cookie: `refreshToken=${encodeURIComponent(refreshToken)}`,| Cookie: `refreshToken=${refreshToken}`, | |
| Cookie: `refreshToken=${encodeURIComponent(refreshToken)}`, |
| staleTime: (query) => { | ||
| if ( | ||
| query.state.status === 'error' || | ||
| query.state.data === null | ||
| ) { | ||
| return 0; | ||
| } | ||
| return 13 * 60 * 1000; // 13분 | ||
| }, |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
새로 추가된 동적 staleTime 로직에 대한 주석이 없습니다. 이는 SSR Prefetch 401 엣지케이스를 해결하기 위한 중요한 변경사항이므로 코드에 설명을 추가하는 것이 좋습니다.
제안:
staleTime: (query) => {
// SSR에서 401 에러가 발생한 경우 즉시 재검증하도록 설정
// 클라이언트에서 즉시 다시 유저 정보를 조회할 수 있음
if (
query.state.status === 'error' ||
query.state.data === null
) {
return 0;
}
return 13 * 60 * 1000; // 13분
},| logout(); | ||
| router.push('/'); |
Copilot
AI
Nov 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logout() 호출 직후 router.push('/')를 실행하면 로그아웃 API 요청이 완료되기 전에 페이지 이동이 발생할 수 있습니다. 이는 비동기 작업이므로 완료를 기다려야 합니다.
문제점:
logout()은mutate함수로 비동기 작업을 시작만 함- 즉시
router.push('/')가 실행되어 페이지 이동 - 로그아웃 API 요청이 취소되거나 완료되지 않을 수 있음
제안:
const handleLogout = () => {
logout(undefined, {
onSettled: () => {
router.push('/');
}
});
};또는 useLogout 훅의 onSuccess 콜백에서 리다이렉트를 처리하는 방법도 있습니다.
| logout(); | |
| router.push('/'); | |
| logout(undefined, { | |
| onSettled: () => { | |
| router.push('/'); | |
| }, | |
| }); |
📦 번들 분석 결과📊 번들 크기 요약
🔍 주요 청크 파일 (크기순)🤖 자동 생성된 번들 분석 리포트 |
⚡ Lighthouse 성능 분석 결과📊 전체 평균 점수
📈 측정 현황
📄 페이지별 상세 분석🏠 커뮤니티 페이지:
|
| 지표 | 점수 |
|---|---|
| 🚀 Performance | 68점 |
| ♿ Accessibility | 78점 |
| ✅ Best Practices | 100점 |
| 🔍 SEO | 100점 |
📊 상세 분석 보기
👥 창업자 페이지: /main/founder
| 지표 | 점수 |
|---|---|
| 🚀 Performance | 75점 |
| ♿ Accessibility | 87점 |
| ✅ Best Practices | 100점 |
| 🔍 SEO | 100점 |
📊 상세 분석 보기
🏡 홈 페이지: /main/home
| 지표 | 점수 |
|---|---|
| 🚀 Performance | 75점 |
| ♿ Accessibility | 91점 |
| ✅ Best Practices | 100점 |
| 🔍 SEO | 100점 |
📊 상세 분석 보기
🗺️ 지도 페이지: /main/maps
| 지표 | 점수 |
|---|---|
| 🚀 Performance | 75점 |
| ♿ Accessibility | 87점 |
| ✅ Best Practices | 100점 |
| 🔍 SEO | 100점 |
📊 상세 분석 보기
👤 프로필 페이지: /main/profile
| 지표 | 점수 |
|---|---|
| 🚀 Performance | 75점 |
| ♿ Accessibility | 88점 |
| ✅ Best Practices | 100점 |
| 🔍 SEO | 100점 |
📊 상세 분석 보기
🔗 전체 상세 분석 결과
📄 측정된 페이지
- /main/community
- /main/founder
- /main/home
- /main/maps
- /main/profile
모든 페이지에서 성능 측정이 완료되었습니다.
🤖 자동 생성된 Lighthouse 성능 리포트
youdaeng2
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수정사항 잘 읽었습니다 고생하셨습니다. 제 로컬에서 있던 문제도 이 pr로 해결될 것 같네요!
📌 개요
SOS-41 브랜치에서 구현한 HttpOnly Cookie 기반 인증 시스템의 엣지 케이스 해결 및 레거시 코드 정리 작업입니다.
🗒 상세 설명
1. 미들웨어 SocketError 해결
파일: apps/web/src/middleware.ts:64-99
미들웨어에서 토큰 갱신 시 발생하던 순환 참조 문제를 해결했습니다.
핵심 기술 및 구현사항
localhost:3000/api/auth/refresh호출 → 자기 자신에게 요청 → SocketError문제 오류 로그
사용 예시
2. 쿠키 Domain 속성 제거
파일: apps/web/src/middleware.ts:19-21, apps/web/src/middleware.ts:102-104
백엔드에서 전달받은
Set-Cookie헤더의 Domain 속성을 제거하여 로컬 환경에서도 쿠키가 정상적으로 저장되도록 수정했습니다.핵심 기술 및 구현사항
Domain=soso.dreampaste.com포함한 쿠키 전송 → localhost에서 저장 안 됨사용 예시
Before-After
Before:
After:
3. SSR Prefetch 401 엣지케이스 해결
파일: apps/web/src/providers/AuthHydrationProvider.tsx:38-46
SSR Prefetch 시 401 에러가 발생했을 때 캐싱되는 문제를 해결했습니다.
핵심 기술 및 구현사항
null로 13분간 캐싱 → 클라이언트에서 재시도 안 됨staleTime: 0으로 설정하여 즉시 재검증사용 예시
Before-After
Before: SSR 401 에러 → 13분간 캐싱 → 클라이언트에서 재시도 없음
After: SSR 401 에러 → 즉시 stale → 클라이언트에서 즉시 재시도
4. 레거시 authStore 제거
파일:
apps/web/src/stores/authStore.ts(삭제됨)더 이상 사용하지 않는 Zustand 기반 인증 상태 관리를 제거했습니다.
핵심 기술 및 구현사항
사용 예시
5. 인증 관련 페이지 정리
파일:
회원가입, 로그인, 프로필 페이지의 인증 처리 로직을 개선했습니다.
핵심 기술 및 구현사항
사용 예시
📸 스크린샷
UI 변경 없음 (백엔드 인증 로직 버그 수정 및 코드 정리)
AS-IS
TO-BE
🔗 이슈
closes #80
✅ 체크리스트
🧪 테스트 방법
엣지 케이스 및 버그 수정을 검증했습니다.
1. 미들웨어 SocketError 해결 검증
2. 쿠키 Domain 속성 제거 검증
3. SSR Prefetch 401 엣지케이스 검증
4. 레거시 authStore 제거 검증
useAuthStore사용처가 모두 제거되었는지 확인5. 인증 페이지 정리 검증
📝 추가 노트
엣지 케이스 수정 내역
이번 PR은 SOS-41에서 구현한 인증 시스템의 버그 및 엣지 케이스를 수정하는 작업입니다.
1. SocketError (순환 참조)
미들웨어에서
localhost:3000/api/auth/refresh를 호출하면 다시 미들웨어가 실행되어 무한 루프가 발생했습니다. 백엔드 API를 직접 호출하도록 수정하여 해결했습니다.2. Cookie Domain 불일치
백엔드에서 프로덕션 도메인(
soso.dreampaste.com)을 포함한 쿠키를 전송하면 로컬 환경(localhost)에서 저장되지 않았습니다. 미들웨어에서 Domain 속성을 제거하여 현재 환경에 맞게 자동 설정되도록 했습니다.3. SSR Prefetch 캐싱 문제
SSR에서 401 에러가 발생하면 React Query가
null을 13분간 캐싱하여 클라이언트에서 재시도하지 않았습니다. 동적staleTime을 사용하여 에러 시 즉시 재검증하도록 수정했습니다.코드 정리 내역
1. Zustand 스토어 제거
HttpOnly Cookie + React Query로 완전히 전환했으므로 더 이상 필요 없는 Zustand 기반
authStore를 제거했습니다.2. 중복 로직 제거
미들웨어에서 이미 인증 체크를 하므로, 클라이언트 컴포넌트의 중복 리다이렉트 로직(
RedirectIfAuthed)을 제거했습니다.