# Token Management System Guide 완전한 Access Token & Refresh Token 시스템 구현 가이드 ## 📋 목차 1. [시스템 개요](#시스템-개요) 2. [토큰 라이프사이클](#토큰-라이프사이클) 3. [API 엔드포인트](#api-엔드포인트) 4. [자동 토큰 갱신](#자동-토큰-갱신) 5. [사용 예시](#사용-예시) 6. [보안 고려사항](#보안-고려사항) --- ## 시스템 개요 ### 토큰 구조 ```json { "access_token": "214|EU7drdTBYN1fru0MylLXwjJbi2svXcikn5ofvmTI354d09c7", "refresh_token": "215|6hAPWcO05jtfSDV9Yz4kLQi3qZDFuycMqrNITOV3c27bd0cb", "token_type": "Bearer", "expires_in": 7200, "expires_at": "2025-11-10 15:49:38" } ``` ### 저장 방식 **HttpOnly 쿠키** (XSS 공격 방지): - `access_token`: 2시간 만료 (7200초) - `refresh_token`: 7일 만료 (604800초) **보안 속성**: - `HttpOnly`: JavaScript 접근 불가 - `Secure`: HTTPS만 전송 - `SameSite=Strict`: CSRF 공격 방지 --- ## 토큰 라이프사이클 ### 1. 로그인 (Token 발급) ``` 사용자 로그인 ↓ POST /api/auth/login ↓ PHP Backend /api/v1/login ↓ access_token + refresh_token 발급 ↓ HttpOnly 쿠키에 저장 ↓ 대시보드로 이동 ``` ### 2. 인증된 요청 ``` 보호된 페이지 접근 ↓ Middleware 인증 체크 ↓ access_token 존재? ├─ Yes → 접근 허용 └─ No → refresh_token 확인 ├─ 있음 → 자동 갱신 시도 └─ 없음 → 로그인 페이지로 ``` ### 3. 토큰 갱신 ``` access_token 만료 (2시간 후) ↓ 보호된 API 호출 시도 ↓ 401 Unauthorized 응답 ↓ POST /api/auth/refresh ↓ refresh_token으로 새 토큰 발급 ↓ 새 access_token + refresh_token 쿠키 업데이트 ↓ 원래 API 호출 재시도 ↓ 성공 ``` ### 4. 로그아웃 ``` 사용자 로그아웃 ↓ POST /api/auth/logout ↓ PHP Backend /api/v1/logout (토큰 무효화) ↓ HttpOnly 쿠키 삭제 ↓ 로그인 페이지로 이동 ``` --- ## API 엔드포인트 ### 1. Login API **Endpoint**: `POST /api/auth/login` **Request**: ```typescript { user_id: string; user_pwd: string; } ``` **Response**: ```typescript { message: string; user: UserObject; tenant: TenantObject | null; menus: MenuItem[]; token_type: "Bearer"; expires_in: number; expires_at: string; } ``` **쿠키 설정**: - `access_token` (HttpOnly, 2시간) - `refresh_token` (HttpOnly, 7일) --- ### 2. Refresh Token API **Endpoint**: `POST /api/auth/refresh` **쿠키 필요**: `refresh_token` **Response** (성공): ```typescript { message: "Token refreshed successfully"; token_type: "Bearer"; expires_in: number; expires_at: string; } ``` **Response** (실패): ```typescript { error: "Token refresh failed"; needsReauth: true; } ``` **쿠키 업데이트**: - 새 `access_token` (2시간) - 새 `refresh_token` (7일) --- ### 3. Auth Check API **Endpoint**: `GET /api/auth/check` **기능**: 1. `access_token` 존재 → 200 OK with `authenticated: true` 2. `access_token` 없음 + `refresh_token` 있음 → 자동 갱신 시도 - 갱신 성공 → 200 OK with `authenticated: true, refreshed: true` - 갱신 실패 → 401 Unauthorized 3. 둘 다 없음 → 401 Unauthorized **Response**: ```typescript // ✅ 인증 성공 (200) { authenticated: true; refreshed?: boolean; // 자동 갱신 여부 } // ❌ 인증 실패 (401) { error: string; // 'Not authenticated' 또는 'Token refresh failed' } ``` **참고**: - 🔵 **Next.js 내부 API** (PHP 백엔드 X) - 성능 최적화: 로컬 쿠키만 확인하여 빠른 응답 - 로그인/회원가입 페이지에서 이미 로그인된 사용자를 대시보드로 리다이렉트하는 데 사용 --- ### 4. Logout API **Endpoint**: `POST /api/auth/logout` **기능**: 1. PHP 백엔드에 로그아웃 요청 (토큰 무효화) 2. `access_token`, `refresh_token` 쿠키 삭제 --- ## 자동 토큰 갱신 ### 1. Middleware에서 자동 갱신 `src/middleware.ts`: ```typescript // access_token 또는 refresh_token이 있으면 인증됨 const accessToken = request.cookies.get('access_token'); const refreshToken = request.cookies.get('refresh_token'); if ((accessToken && accessToken.value) || (refreshToken && refreshToken.value)) { return { isAuthenticated: true, authMode: 'bearer' }; } ``` ### 2. Auth Check에서 자동 갱신 `src/app/api/auth/check/route.ts`: ```typescript // access_token 없고 refresh_token만 있으면 자동 갱신 if (refreshToken && !accessToken) { const refreshResponse = await fetch('/api/v1/refresh', {...}); // 새 토큰을 HttpOnly 쿠키로 설정 } ``` ### 3. API Client에서 자동 갱신 `src/lib/api/client.ts`: ```typescript // withTokenRefresh 헬퍼 함수 사용 const data = await withTokenRefresh(() => apiClient.get('/protected/resource') ); ``` **동작 방식**: 1. API 호출 시도 2. 401 응답 받음 3. `/api/auth/refresh` 호출 4. 성공 시 원래 API 재시도 5. 실패 시 로그인 페이지로 리다이렉트 --- ## 사용 예시 ### 예시 1: 보호된 페이지에서 API 호출 ```typescript // src/app/[locale]/(protected)/dashboard/page.tsx import { withTokenRefresh } from '@/lib/api/client'; export default function Dashboard() { const fetchData = async () => { try { // 자동 토큰 갱신 포함 const data = await withTokenRefresh(() => fetch('/api/protected/data', { credentials: 'include' // 쿠키 포함 }) ); console.log('Data fetched:', data); } catch (error) { console.error('Fetch failed:', error); } }; return
...
; } ``` ### 예시 2: 수동 토큰 갱신 ```typescript // src/lib/auth/token-refresh.ts import { refreshTokenClient } from '@/lib/auth/token-refresh'; async function handleProtectedAction() { try { // API 호출 const response = await fetch('/api/protected/action'); if (!response.ok) { // 401 에러 시 토큰 갱신 시도 const refreshed = await refreshTokenClient(); if (refreshed) { // 재시도 return await fetch('/api/protected/action'); } } return response; } catch (error) { console.error('Action failed:', error); } } ``` ### 예시 3: Protected Layout ```typescript // src/app/[locale]/(protected)/layout.tsx "use client"; import { useAuthGuard } from '@/hooks/useAuthGuard'; export default function ProtectedLayout({ children }) { // 자동으로 /api/auth/check 호출 // access_token 없으면 refresh_token으로 자동 갱신 useAuthGuard(); return <>{children}; } ``` --- ## 보안 고려사항 ### ✅ 구현된 보안 기능 1. **HttpOnly 쿠키** - JavaScript에서 토큰 접근 불가 - XSS 공격으로부터 보호 2. **Secure 플래그** - HTTPS에서만 쿠키 전송 - 중간자 공격 방지 3. **SameSite=Strict** - CSRF 공격 방지 - 크로스 사이트 요청 차단 4. **토큰 만료 시간** - Access Token: 2시간 (짧은 수명) - Refresh Token: 7일 (긴 수명) 5. **에러 메시지 일반화** - 백엔드 상세 에러 노출 방지 - 정보 유출 차단 ### ⚠️ 추가 권장 사항 1. **Token Rotation** - Refresh 시 새로운 refresh_token 발급 (현재 구현됨 ✅) 2. **Rate Limiting** - 로그인 시도 제한 - Refresh 요청 제한 3. **IP 검증** - 토큰 발급 시 IP 기록 - 다른 IP에서 사용 시 경고 4. **Device Fingerprinting** - 토큰 발급 디바이스 기록 - 이상 접근 탐지 5. **Logout Blacklist** - 로그아웃 된 토큰 블랙리스트 관리 - 재사용 방지 --- ## 트러블슈팅 ### 문제 1: 로그인 후 바로 로그아웃됨 **원인**: 쿠키가 설정되지 않음 **해결**: 1. 브라우저 개발자 도구 → Application → Cookies 확인 2. `access_token`, `refresh_token` 존재 확인 3. 없으면 `/api/auth/login` 응답 헤더 확인 ### 문제 2: Token refresh 무한 루프 **원인**: Refresh token도 만료됨 **해결**: 1. `/api/auth/refresh` 응답 확인 2. 401 응답 시 로그인 페이지로 리다이렉트 3. `needsReauth: true` 플래그 확인 ### 문제 3: CORS 에러 **원인**: 크로스 도메인 요청 시 쿠키 전송 실패 **해결**: ```typescript fetch('/api/protected', { credentials: 'include' // 쿠키 포함 }) ``` --- ## 참고 파일 - `src/app/api/auth/login/route.ts` - 로그인 API - `src/app/api/auth/refresh/route.ts` - 토큰 갱신 API - `src/app/api/auth/check/route.ts` - 인증 체크 API - `src/app/api/auth/logout/route.ts` - 로그아웃 API - `src/middleware.ts` - 인증 미들웨어 - `src/lib/auth/token-refresh.ts` - 토큰 갱신 유틸리티 - `src/lib/api/client.ts` - API 클라이언트 (자동 갱신)