Skip to content

feat: 로그인 안한 상태로 페이지 접근 시 제한#42

Merged
DongEun02 merged 3 commits into
devfrom
feature/layout
Jan 25, 2026
Merged

feat: 로그인 안한 상태로 페이지 접근 시 제한#42
DongEun02 merged 3 commits into
devfrom
feature/layout

Conversation

@DongEun02
Copy link
Copy Markdown
Contributor

@DongEun02 DongEun02 commented Jan 21, 2026

1) 작업한 이슈번호

2) 변경 요약 (What & Why)

  • 무엇을 변경했는지: 사용자 정보 없을 시 페이지 접근 제한, useAuthGuard 커스텀 훅 사용
  • 변경했는지(문제/목표):

3) 스크린샷/동영상 (UI 변경 시)

전/후 비교, 반응형(모바일/데스크톱) 캡쳐

  • Before:
  • After:

4) 상세 변경사항

  • 라우팅/페이지:
  • 컴포넌트:
  • 상태관리:
  • API 호출:
  • 스타일:
  • 기타:

5) 참고사항

Summary by CodeRabbit

  • New Features

    • 인증 기반 접근 제어 추가 — 루트 및 랜딩 페이지 간 자동 리디렉션 적용
    • 클라이언트 쪽 인증 검사 도입으로 보호된 레이아웃에 로딩/접근 흐름 추가
    • 로그인 시 상태를 쿠키에 저장해 이후 접근 허용 구현
  • Bug Fixes

    • 로그아웃 후 상태 정리 및 안내 메시지 표시 동작 개선
    • 불필요한 로딩 상태 제거로 렌더링 단순화(초기에는 빈 글 안내가 잠깐 보일 수 있음)

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 21, 2026

📝 Walkthrough

Walkthrough

클라이언트 측 인증 훅 useAuthGuard를 추가하고 사이드바 레이아웃에서 이를 사용해 인증을 검사하도록 했습니다. 로그인 시 isLoggedIn 쿠키를 설정하고 로그아웃 시 해당 쿠키를 제거해 리다이렉트/알림 로직을 변경했으며, 일부 컴포넌트에서 불필요한 로딩 변수 분기를 제거했습니다. (약 47자)

Changes

응집 / 파일(들) 변경 요약
인증 훅 추가
hooks/useAuthGuard.ts
Firebase 인증 상태 구독 및 즉시 리다이렉트 로직을 가진 클라이언트 훅 useAuthGuard 추가.
레이아웃 인증 적용
app/(with-sidebar)/layout.tsx
'use client' 추가하고 useAuthGuard 호출로 로딩 표시 및 미인증 시 렌더링 중단/리다이렉트 적용.
로그인 쿠키 설정
components/auth/LoginForm.tsx
로그인 성공 후 isLoggedIn 쿠키를 7일간 설정하도록 setLoginCookie 호출 추가 (GitHub 및 이메일 로그인 흐름).
로그아웃 UX 변경
components/common/Sidebar.tsx
로그아웃 후 isLoggedIn 쿠키 제거, alert('로그아웃 되었습니다.') 표시, router.replace('/landing')로 이동; 기존 router.refresh() 제거.
로딩 상태 제거
components/write/PostDetail.tsx
내부 loading 상태 변수 및 분기 삭제; 데이터 존재 여부로 렌더링 제어.
라우팅 미들웨어
middleware.ts
isLoggedIn 쿠키 기반으로 //landing 간 접근 제어하는 Next.js 미들웨어 추가 (matcher: ['/', '/landing']).

Sequence Diagram(s)

sequenceDiagram
    participant User as 사용자
    participant Layout as app/(with-sidebar)/layout.tsx
    participant Hook as hooks/useAuthGuard.ts
    participant Firebase as Firebase Auth
    participant Router as Next.js Router
    participant Sidebar as components/common/Sidebar.tsx

    User->>Layout: 사이드바 페이지 접근
    Layout->>Hook: useAuthGuard() 호출
    Hook->>Firebase: onAuthStateChanged 구독
    Firebase-->>Hook: 인증 상태 이벤트

    alt 로딩 중
        Hook-->>Layout: { loading: true }
        Layout-->>User: "로딩중..." 표시
    else 미인증
        Hook-->>Router: router.replace('/login')
        Router-->>User: 로그인 페이지로 리다이렉트
    else 인증됨
        Hook-->>Layout: { isAuthed: true, user }
        Layout-->>Sidebar: 사이드바 및 자식 렌더링
        User->>Sidebar: 로그아웃 클릭
        Sidebar->>Firebase: signOut()
        Firebase-->>Sidebar: signOut 완료
        Sidebar-->>User: alert("로그아웃 되었습니다.")
        Sidebar-->>Router: router.replace('/landing')
        Router-->>User: 랜딩 페이지로 리다이렉트
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🐰 훅으로 문을 지키고, 쿠키로 길을 나눠요.
로딩은 잠깐 숨고, 알림은 반짝 빛나죠.
깡총 뛰며 새 길을 열었으니,
모두 모여 축하하자요! 🎉🐇

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning PR 설명이 템플릿 구조를 따르지만 대부분의 섹션이 비어있거나 불완전합니다. '무엇을' 변경했는지만 간단히 기술되어 있고, '왜' 변경했는지(문제/목표), 상세 변경사항 등이 구체적으로 작성되지 않았습니다. 변경 이유(문제 상황 및 목표)를 명확히 작성하고, 상세 변경사항에서 라우팅, 컴포넌트, 상태관리 등 구체적인 변경 내용을 기술해주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed 제목은 PR의 주요 변경사항을 명확하게 설명합니다. 로그인하지 않은 상태에서 페이지 접근을 제한하는 것이 이 PR의 핵심 목표이며, 제목이 이를 잘 반영합니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@app/`(with-sidebar)/layout.tsx:
- Around line 17-20: The layout currently calls alert('로그인이 필요합니다.') directly
during render when isAuthed is false, which is an anti-pattern; remove the alert
from the render path and either (a) simply return null when isAuthed is false
(since useAuthGuard already handles redirect), or (b) if you must show a
one-time alert, trigger it inside a useEffect in the layout component (or inside
useAuthGuard) so it runs only once on change of isAuthed; update the conditional
in layout.tsx (the component using isAuthed) accordingly and ensure no side
effects occur during render.

In `@hooks/useAuthGuard.ts`:
- Line 22: The default redirectTo in useAuthGuard ('/login') conflicts with
Sidebar.tsx which redirects to '/landing' on logout, causing a second redirect
when onAuthStateChanged fires; fix by making redirect behavior consistent—either
change useAuthGuard's default redirectTo to '/landing' or add an escape flag
(e.g., skipRedirectOnLogout) to useAuthGuard and have Sidebar set that flag when
calling signOut so onAuthStateChanged does not trigger the guard redirect;
update the useAuthGuard logic (and the Sidebar logout call) to check this new
flag or new default and only perform redirect when appropriate.
- Around line 28-41: The onAuthStateChanged handler currently sets
setLoading(true) when u is null which leaves loading stuck true and causes
premature redirects; update the effect (the onAuthStateChanged callback in
useEffect) to always setLoading(false) after processing the auth result and
avoid redirecting on the initial transient null by introducing an initialization
guard (e.g., a ref or an "initialized" state): when the callback runs, setUser(u
?? null), setLoading(false), and if u is null only call
router.replace(redirectTo) after the initialization flag is true (set the flag
to true on the first callback) so transient nulls don’t trigger redirects (refer
to the onAuthStateChanged callback, setUser, setLoading, router.replace, and
redirectTo).
🧹 Nitpick comments (2)
components/common/Sidebar.tsx (1)

26-35: 로그아웃 로직 변경 확인됨

로그아웃 성공 시 사용자에게 피드백을 제공하는 것은 좋습니다. 다만 alert()은 브라우저를 블로킹하고 UX 측면에서 다소 구식입니다. 추후 toast 알림(예: react-hot-toast, sonner)으로 개선을 고려해 보세요.

현재 기능적으로는 문제가 없으므로 승인합니다.

app/(with-sidebar)/layout.tsx (1)

16-16: 로딩 UI 개선 고려

현재 단순 텍스트 "로딩중..."을 표시하고 있습니다. UX 향상을 위해 스피너나 스켈레톤 UI 사용을 고려해 보세요.

Comment thread app/(with-sidebar)/layout.tsx
Comment thread hooks/useAuthGuard.ts
Comment thread hooks/useAuthGuard.ts
Comment on lines +28 to +41
useEffect(() => {
const unsub = onAuthStateChanged(auth, (u) => {
if (!u) {
setUser(null);
setLoading(true);
router.replace(redirectTo);
return;
}
setUser(u);
setLoading(false);
});

return () => unsub();
}, [router, redirectTo]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

loading 상태가 미인증 시 false로 전환되지 않는 문제

사용자가 미인증일 때 setLoading(true)를 호출하고 있어 loading이 계속 true인 채로 남습니다. 이로 인해:

  1. layout.tsx에서 if (!isAuthed) 블록의 alert가 절대 실행되지 않음 (loadingtrue이므로 먼저 반환됨)
  2. 리다이렉트 중 사용자가 "로딩중..." 화면만 계속 보게 됨

또한 Firebase onAuthStateChanged는 초기에 persisted auth state를 확인하기 전에 null로 콜백될 수 있어, 실제로 로그인된 사용자도 잠시 미인증으로 판단되어 불필요한 리다이렉트가 발생할 수 있습니다.

🔧 수정 제안
 useEffect(() => {
   const unsub = onAuthStateChanged(auth, (u) => {
     if (!u) {
       setUser(null);
-      setLoading(true);
+      setLoading(false);
       router.replace(redirectTo);
       return;
     }
     setUser(u);
     setLoading(false);
   });

   return () => unsub();
 }, [router, redirectTo]);
🤖 Prompt for AI Agents
In `@hooks/useAuthGuard.ts` around lines 28 - 41, The onAuthStateChanged handler
currently sets setLoading(true) when u is null which leaves loading stuck true
and causes premature redirects; update the effect (the onAuthStateChanged
callback in useEffect) to always setLoading(false) after processing the auth
result and avoid redirecting on the initial transient null by introducing an
initialization guard (e.g., a ref or an "initialized" state): when the callback
runs, setUser(u ?? null), setLoading(false), and if u is null only call
router.replace(redirectTo) after the initialization flag is true (set the flag
to true on the first callback) so transient nulls don’t trigger redirects (refer
to the onAuthStateChanged callback, setUser, setLoading, router.replace, and
redirectTo).

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@middleware.ts`:
- Around line 4-22: The middleware currently trusts the client-set isLoggedIn
cookie (set in LoginForm.tsx via document.cookie) allowing auth bypass; update
middleware(request: NextRequest) to validate authentication server-side instead
of reading this client cookie — either verify Firebase ID/session tokens using
the Firebase Admin SDK or switch to Firebase Auth createSessionCookie() and read
an HttpOnly, Secure session cookie in middleware, and replace the simple cookie
check with token verification logic before calling NextResponse.redirect/new
URL(...) or NextResponse.next(); also ensure every code path returns explicitly
(add an explicit NextResponse.next() at the end of middleware) so intent is
clear.
🧹 Nitpick comments (2)
components/auth/LoginForm.tsx (1)

25-29: 쿠키 설정 시 보안 속성 추가 권장

쿠키에 SameSiteSecure 속성이 누락되어 있습니다. 프로덕션 환경에서는 보안을 위해 이 속성들을 추가하는 것이 좋습니다.

또한 중요한 보안 고려사항: 이 쿠키는 클라이언트에서 설정되고 HttpOnly가 아니므로, 악의적인 사용자가 수동으로 isLoggedIn=true 쿠키를 설정하여 미들웨어 인증을 우회할 수 있습니다. 미들웨어에서 실제 Firebase 세션 토큰을 검증하거나, 서버 측에서 쿠키를 설정하는 방식을 고려해보세요.

🔒 쿠키 속성 개선 제안
  const setLoginCookie = () => {
-   // 7일간 유지되는 쿠키 설정
-   document.cookie = 'isLoggedIn=true; path=/; max-age=' + 60 * 60 * 24 * 7;
+   // 7일간 유지되는 쿠키 설정 (보안 속성 포함)
+   const maxAge = 60 * 60 * 24 * 7;
+   document.cookie = `isLoggedIn=true; path=/; max-age=${maxAge}; SameSite=Lax${window.location.protocol === 'https:' ? '; Secure' : ''}`;
  };
middleware.ts (1)

24-26: 보호 경로 범위 확인 필요

현재 matcher는 //landing만 커버합니다. 다른 보호가 필요한 경로(예: /write, /profile 등)는 클라이언트 측 useAuthGuard에 의존하게 됩니다.

서버 측에서도 이러한 경로들을 보호하려면 matcher 확장을 고려해보세요.

♻️ 확장된 matcher 예시
export const config = {
- matcher: ['/', '/landing'],
+ matcher: ['/', '/landing', '/write/:path*', '/profile/:path*'],
};

Comment thread middleware.ts
Comment on lines +4 to +22
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;

// 쿠키에서 isLoggedIn 확인
const isLoggedIn = request.cookies.get('isLoggedIn')?.value === 'true';

if (pathname === '/') {
// 로그인이 안 된 경우에만 랜딩으로 보냄
if (!isLoggedIn) {
return NextResponse.redirect(new URL('/landing', request.url));
}
// 로그인 된 상태라면 그대로 루트('/')를 보여줌
return NextResponse.next();
}

if (pathname === '/landing' && isLoggedIn) {
return NextResponse.redirect(new URL('/', request.url));
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find the middleware file and related auth configuration
git ls-files | grep -E "(middleware|auth|cookie|firebase)" | head -20

Repository: DeveloperBlog-Devflow/devflow

Length of output: 428


🏁 Script executed:

# Look for where isLoggedIn cookie is set
rg "isLoggedIn" -t typescript -t javascript -B 3 -A 3

Repository: DeveloperBlog-Devflow/devflow

Length of output: 107


🏁 Script executed:

# Search for cookie configuration and HttpOnly settings
rg "cookies\." -t typescript -t javascript -B 2 -A 2 | head -50

Repository: DeveloperBlog-Devflow/devflow

Length of output: 107


🏁 Script executed:

# Check for Firebase configuration
rg -i "firebase" -t typescript -t javascript -t json | head -20

Repository: DeveloperBlog-Devflow/devflow

Length of output: 107


🏁 Script executed:

# Look for auth-related setup and session configuration
fd -e "ts" -e "js" -e "json" | xargs grep -l -E "(auth|session|login)" 2>/dev/null | head -10

Repository: DeveloperBlog-Devflow/devflow

Length of output: 160


🏁 Script executed:

# Read the middleware file completely
cat -n middleware.ts

Repository: DeveloperBlog-Devflow/devflow

Length of output: 936


🏁 Script executed:

# Look for where isLoggedIn cookie is set (likely in LoginForm)
cat -n "components/auth/LoginForm.tsx"

Repository: DeveloperBlog-Devflow/devflow

Length of output: 4636


🏁 Script executed:

# Check the Firebase configuration
cat -n "lib/firebase.ts"

Repository: DeveloperBlog-Devflow/devflow

Length of output: 1373


🏁 Script executed:

# Check signup service for auth handling
cat -n "services/auth/signup.service.ts"

Repository: DeveloperBlog-Devflow/devflow

Length of output: 1900


🏁 Script executed:

# Search for any server-side auth handling or API routes
fd -e ts -e js | xargs grep -l "NextRequest\|NextResponse\|route.ts" 2>/dev/null | head -10

Repository: DeveloperBlog-Devflow/devflow

Length of output: 86


🏁 Script executed:

# Check if there are any server-side auth routes or session handling
fd -e ts -e js "api.*auth\|api.*session\|route" 2>/dev/null | head -10

Repository: DeveloperBlog-Devflow/devflow

Length of output: 55


🏁 Script executed:

# Look for any logout functionality or cookie clearing
rg "isLoggedIn|cookies\|logout" --type ts --type js -B 2 -A 2 | head -40

Repository: DeveloperBlog-Devflow/devflow

Length of output: 1324


보안 취약점: 클라이언트 쿠키 기반 라우팅 인증 우회 가능

현재 미들웨어는 isLoggedIn 쿠키의 값만으로 라우팅을 결정하고 있습니다. 이 쿠키는 LoginForm.tsx의 document.cookie = 'isLoggedIn=true'로 클라이언트에서 설정되므로, 악의적인 사용자가 브라우저 콘솔에서 document.cookie = 'isLoggedIn=true'를 실행하여 인증을 우회할 수 있습니다. 미들웨어는 이 쿠키 값을 검증 없이 신뢰하고 있습니다.

이미 Firebase Auth를 통해 실제 인증을 처리하고 있으므로, 다음 중 하나의 방식으로 개선할 것을 권장합니다:

  1. Firebase Admin SDK를 사용하여 미들웨어에서 서버 측 세션 토큰 검증
  2. Firebase Auth의 createSessionCookie() 기능을 활용하여 HttpOnly + Secure 플래그가 적용된 안전한 쿠키 사용

더불어, 명시적인 NextResponse.next() 반환을 추가하면 코드의 의도가 더욱 명확해집니다.

🔒 명시적 반환 추가 제안
  if (pathname === '/landing' && isLoggedIn) {
    return NextResponse.redirect(new URL('/', request.url));
  }
+
+ return NextResponse.next();
}
🤖 Prompt for AI Agents
In `@middleware.ts` around lines 4 - 22, The middleware currently trusts the
client-set isLoggedIn cookie (set in LoginForm.tsx via document.cookie) allowing
auth bypass; update middleware(request: NextRequest) to validate authentication
server-side instead of reading this client cookie — either verify Firebase
ID/session tokens using the Firebase Admin SDK or switch to Firebase Auth
createSessionCookie() and read an HttpOnly, Secure session cookie in middleware,
and replace the simple cookie check with token verification logic before calling
NextResponse.redirect/new URL(...) or NextResponse.next(); also ensure every
code path returns explicitly (add an explicit NextResponse.next() at the end of
middleware) so intent is clear.

@DongEun02 DongEun02 merged commit 0f011da into dev Jan 25, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant