|
1 | 1 | # Authentication & Authorization |
2 | 2 |
|
3 | | -## Login Redirect Flow |
| 3 | +## Overview |
4 | 4 |
|
5 | | -### Overview |
| 5 | +웹 인증은 **클라이언트 재발급/인터셉터 중심**으로 동작합니다. |
6 | 6 |
|
7 | | -The application implements a comprehensive login redirect system that ensures users are properly authenticated before accessing protected pages. |
| 7 | +- 서버 진입 시 middleware에서 보호 경로를 `/login`으로 선리다이렉트하지 않습니다. |
| 8 | +- 인증 실패 시점은 페이지 렌더/데이터 요청 단계에서 결정됩니다. |
8 | 9 |
|
9 | | -### Protected Pages |
| 10 | +## Current Flow |
10 | 11 |
|
11 | | -The following pages require authentication: |
12 | | -- `/mentor/*` - All mentor-related pages |
13 | | -- `/my/*` - All user profile pages |
14 | | -- `/community` and `/community/*` - Entire community experience, including board lists, post detail, creation, and modification |
| 12 | +### 1. App Initialization |
15 | 13 |
|
16 | | -### How It Works |
| 14 | +- `ReissueProvider`에서 앱 최초 진입 시 `/auth/reissue`를 시도합니다. |
| 15 | +- 성공 시 access token이 스토어에 반영되고, 실패 시 비로그인 상태를 유지합니다. |
17 | 16 |
|
18 | | -#### 1. Middleware Detection |
| 17 | +### 2. Request Interceptor |
19 | 18 |
|
20 | | -The middleware (`apps/web/src/middleware.ts`) checks for authentication on every request: |
| 19 | +- `axiosInstance` 요청 인터셉터가 access token 만료 여부를 검사합니다. |
| 20 | +- 토큰이 없거나 만료된 경우 재발급을 시도하고, 실패하면 로그인 이동을 유도합니다. |
21 | 21 |
|
22 | | -```typescript |
23 | | -const loginNeedPages = ["/mentor", "/my", "/community"]; |
24 | | -const needLogin = loginNeedPages.some((path) => { |
25 | | - return url.pathname === path || url.pathname.startsWith(`${path}/`); |
26 | | -}); |
27 | | -``` |
| 22 | +### 3. Response Interceptor |
28 | 23 |
|
29 | | -#### 2. Community Redirect Reason |
| 24 | +- API 응답이 401이면 재발급 1회 후 원요청을 재시도합니다. |
| 25 | +- 재시도 실패 시 로그인 페이지로 이동합니다. |
30 | 26 |
|
31 | | -When an unauthenticated user tries to access a protected page: |
32 | | -- Middleware redirects to `/login` |
33 | | -- Community routes include a reason marker so the login page can explain why access was blocked |
34 | | -- Example: `/login?reason=community-members-only` |
| 27 | +### 4. Page-level Guards |
35 | 28 |
|
36 | | -```typescript |
37 | | -if (needLogin && !refreshToken) { |
38 | | - const isCommunityRoute = url.pathname === "/community" || url.pathname.startsWith("/community/"); |
39 | | - url.pathname = "/login"; |
40 | | - if (isCommunityRoute) { |
41 | | - url.searchParams.set("reason", "community-members-only"); |
42 | | - } |
43 | | - return NextResponse.redirect(url); |
44 | | -} |
45 | | -``` |
| 29 | +- 인증이 필요한 UI(예: 멘토 페이지)는 클라이언트에서 재발급/토큰 상태를 확인합니다. |
| 30 | +- 필요한 경우 페이지 내부 로직에서 `/login`으로 이동합니다. |
46 | 31 |
|
47 | | -#### 3. Toast Notification |
| 32 | +## Middleware Responsibility |
48 | 33 |
|
49 | | -The login page displays a one-time toast message when users are redirected from community routes: |
| 34 | +`apps/web/src/middleware.ts`는 현재 아래만 담당합니다. |
50 | 35 |
|
51 | | -```typescript |
52 | | -// apps/web/src/app/login/LoginContent.tsx |
53 | | -useEffect(() => { |
54 | | - const reason = searchParams.get("reason"); |
55 | | - if (reason === "community-members-only") { |
56 | | - toast.info("커뮤니티는 회원 전용입니다. 로그인 후 이용해주세요."); |
57 | | - router.replace(pathname); |
58 | | - } |
59 | | -}, [pathname, router, searchParams]); |
60 | | -``` |
| 36 | +- stage 환경 `robots.txt` 제어 |
| 37 | +- 스캐너/프로브 경로 차단 (`.php`, `/.git`, `/wp-admin` 등) |
| 38 | +- 정적 리소스 경로 제외 matcher 유지 |
61 | 39 |
|
62 | | -#### 4. Post-Login Redirect |
| 40 | +즉, middleware는 더 이상 인증 선검증(보호 경로 강제 로그인 리다이렉트)을 수행하지 않습니다. |
63 | 41 |
|
64 | | -After successful authentication, users continue to be redirected to `/`. |
| 42 | +## Tokens |
65 | 43 |
|
66 | | -### Configuration |
| 44 | +### Refresh Token |
67 | 45 |
|
68 | | -Authentication is cookie-based: |
69 | | -- Refresh token: HTTP-only cookie |
70 | | -- Middleware: 보호 페이지 접근 시 refresh token 존재 여부 확인 |
71 | | -- 로그인 성공 후: 메인(`/`)으로 이동 |
| 46 | +- HTTP-only 쿠키로 관리 |
| 47 | +- 재발급 API 호출 시 서버가 검증 |
72 | 48 |
|
73 | | -### Token Management |
| 49 | +### Access Token |
74 | 50 |
|
75 | | -#### Refresh Token |
76 | | -- Stored in HTTP-only cookie (secure) |
77 | | -- Used for authentication checks in middleware |
78 | | -- Automatically renewed on valid requests |
| 51 | +- Zustand store 기반으로 관리 |
| 52 | +- API 인증 헤더에 사용 |
| 53 | +- 만료 시 재발급을 통해 갱신 |
79 | 54 |
|
80 | | -#### Access Token |
81 | | -- Stored in Zustand store or localStorage |
82 | | -- Used for API requests |
83 | | -- Short-lived for security |
| 55 | +## Related Files |
84 | 56 |
|
85 | | -### Adding New Protected Routes |
86 | | - |
87 | | -To protect a new route: |
88 | | - |
89 | | -1. Add to `loginNeedPages` array in middleware: |
90 | | -```typescript |
91 | | -const loginNeedPages = ["/mentor", "/my", "/new-route"]; |
92 | | -``` |
93 | | - |
94 | | -2. Or add custom logic for sub-routes: |
95 | | -```typescript |
96 | | -const isNewRouteSubPath = url.pathname.startsWith("/new-route/"); |
97 | | -const needLogin = loginNeedPages.some(...) || isNewRouteSubPath; |
98 | | -``` |
99 | | - |
100 | | -### Troubleshooting |
101 | | - |
102 | | -#### Redirect not working? |
103 | | -- Verify refresh token exists in cookies |
104 | | -- Check middleware matcher pattern excludes static files |
105 | | - |
106 | | -#### Toast not showing? |
107 | | -- Ensure `reason=community-members-only` query parameter is present for community access |
108 | | -- Check `LoginContent.tsx` useEffect is running |
109 | | -- Verify toast store is initialized |
110 | | - |
111 | | -### Security Considerations |
112 | | - |
113 | | -1. **HTTP-Only Cookies**: Refresh tokens are never accessible to JavaScript |
114 | | -2. **Middleware Protection**: Server-side check before page renders |
115 | | -3. **Token Expiry**: Short-lived access tokens minimize exposure |
116 | | -4. **Scoped Login Reasons**: Community-only messaging is controlled by a fixed internal `reason` value |
117 | | - |
118 | | -### Related Files |
119 | | - |
120 | | -- `apps/web/src/middleware.ts` - Authentication middleware |
121 | | -- `apps/web/src/app/login/LoginContent.tsx` - Login page with redirect handling |
122 | | -- `apps/web/src/lib/zustand/useAuthStore.ts` - Auth state management |
123 | | -- `apps/web/.env` - Configuration |
124 | | - |
125 | | -### Issue Reference |
126 | | - |
127 | | -This implementation resolves issue #302: "로그인 필요 페이지 분리 작업 + proxy 에서 리디렉션 처리" |
128 | | - |
129 | | -The login reason marker and toast notification help users understand why community access was blocked. |
| 57 | +- `apps/web/src/lib/zustand/useAuthStore.ts` |
| 58 | +- `apps/web/src/utils/axiosInstance.ts` |
| 59 | +- `apps/web/src/components/layout/ReissueProvider/index.tsx` |
| 60 | +- `apps/web/src/middleware.ts` |
0 commit comments