- BOM 항목 추가/수정/삭제 시 섹션탭 즉시 반영 - 섹션 복제 시 UI 즉시 업데이트 (null vs undefined 이슈 해결) - 항목 수정 기능 추가 (useTemplateManagement) - 실시간 동기화 문서 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
10 KiB
10 KiB
Token Management System Guide
완전한 Access Token & Refresh Token 시스템 구현 가이드
📋 목차
시스템 개요
토큰 구조
{
"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:
{
user_id: string;
user_pwd: string;
}
Response:
{
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 (성공):
{
message: "Token refreshed successfully";
token_type: "Bearer";
expires_in: number;
expires_at: string;
}
Response (실패):
{
error: "Token refresh failed";
needsReauth: true;
}
쿠키 업데이트:
- 새
access_token(2시간) - 새
refresh_token(7일)
3. Auth Check API
Endpoint: GET /api/auth/check
기능:
access_token존재 → 200 OK withauthenticated: trueaccess_token없음 +refresh_token있음 → 자동 갱신 시도- 갱신 성공 → 200 OK with
authenticated: true, refreshed: true - 갱신 실패 → 401 Unauthorized
- 갱신 성공 → 200 OK with
- 둘 다 없음 → 401 Unauthorized
Response:
// ✅ 인증 성공 (200)
{
authenticated: true;
refreshed?: boolean; // 자동 갱신 여부
}
// ❌ 인증 실패 (401)
{
error: string; // 'Not authenticated' 또는 'Token refresh failed'
}
참고:
- 🔵 Next.js 내부 API (PHP 백엔드 X)
- 성능 최적화: 로컬 쿠키만 확인하여 빠른 응답
⚠️ 2025-11-27 변경사항:
LoginPage.tsx에서 auth/check 호출 제거됨- 제거 이유:
- 미들웨어(
middleware.ts)에서 이미 동일한 처리를 함 (guestOnlyRoutes 리다이렉트)- 401 응답이 Network 탭에 에러로 표시되어 백엔드 개발자 혼란 유발
- 불필요한 API 호출로 인한 성능 저하
- 대체 방안: 미들웨어가 서버 사이드에서 쿠키 체크 후 리다이렉트 처리
- 참고:
src/components/auth/LoginPage.tsx주석 참조
4. Logout API
Endpoint: POST /api/auth/logout
기능:
- PHP 백엔드에 로그아웃 요청 (토큰 무효화)
access_token,refresh_token쿠키 삭제
자동 토큰 갱신
1. Middleware에서 자동 갱신
src/middleware.ts:
// 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:
// access_token 없고 refresh_token만 있으면 자동 갱신
if (refreshToken && !accessToken) {
const refreshResponse = await fetch('/api/v1/refresh', {...});
// 새 토큰을 HttpOnly 쿠키로 설정
}
3. Proxy에서 자동 갱신 (✅ 2025-11-27 구현)
src/app/api/proxy/[...path]/route.ts:
// 401 응답 시 자동 토큰 갱신 후 재시도
if (backendResponse.status === 401 && refreshToken) {
const refreshResult = await refreshAccessToken(refreshToken);
if (refreshResult.success && refreshResult.accessToken) {
// 새 토큰으로 원래 요청 재시도
token = refreshResult.accessToken;
backendResponse = await executeBackendRequest(url, method, token, body, contentType);
// 새 토큰을 쿠키에 저장
createTokenCookies(newTokens).forEach(cookie => {
clientResponse.headers.append('Set-Cookie', cookie);
});
} else {
// 리프레시 실패 → 쿠키 삭제 후 401 반환
return NextResponse.json({ error: 'Authentication failed', needsReauth: true }, { status: 401 });
}
}
동작 방식:
- 백엔드 API 호출 (access_token 사용)
- 401 Unauthorized 응답 받음
- refresh_token으로
/api/v1/refresh호출 - 성공 시: 새 토큰으로 원래 요청 재시도 + 쿠키 업데이트
- 실패 시: 쿠키 삭제 +
needsReauth: true응답
장점: 프론트엔드 코드 수정 없이 모든
/api/proxy/*요청에 자동 토큰 갱신 적용
4. API Client에서 자동 갱신 (Legacy)
src/lib/api/client.ts:
// withTokenRefresh 헬퍼 함수 사용
const data = await withTokenRefresh(() =>
apiClient.get('/protected/resource')
);
동작 방식:
- API 호출 시도
- 401 응답 받음
/api/auth/refresh호출- 성공 시 원래 API 재시도
- 실패 시 로그인 페이지로 리다이렉트
참고: 대부분의 API 호출은 프록시를 통해 자동 갱신되므로 직접 사용할 필요 없음
사용 예시
예시 1: 보호된 페이지에서 API 호출
// 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 <div>...</div>;
}
예시 2: 수동 토큰 갱신
// 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
// 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}</>;
}
보안 고려사항
✅ 구현된 보안 기능
-
HttpOnly 쿠키
- JavaScript에서 토큰 접근 불가
- XSS 공격으로부터 보호
-
Secure 플래그
- HTTPS에서만 쿠키 전송
- 중간자 공격 방지
-
SameSite=Strict
- CSRF 공격 방지
- 크로스 사이트 요청 차단
-
토큰 만료 시간
- Access Token: 2시간 (짧은 수명)
- Refresh Token: 7일 (긴 수명)
-
에러 메시지 일반화
- 백엔드 상세 에러 노출 방지
- 정보 유출 차단
⚠️ 추가 권장 사항
-
Token Rotation
- Refresh 시 새로운 refresh_token 발급 (현재 구현됨 ✅)
-
Rate Limiting
- 로그인 시도 제한
- Refresh 요청 제한
-
IP 검증
- 토큰 발급 시 IP 기록
- 다른 IP에서 사용 시 경고
-
Device Fingerprinting
- 토큰 발급 디바이스 기록
- 이상 접근 탐지
-
Logout Blacklist
- 로그아웃 된 토큰 블랙리스트 관리
- 재사용 방지
트러블슈팅
문제 1: 로그인 후 바로 로그아웃됨
원인: 쿠키가 설정되지 않음
해결:
- 브라우저 개발자 도구 → Application → Cookies 확인
access_token,refresh_token존재 확인- 없으면
/api/auth/login응답 헤더 확인
문제 2: Token refresh 무한 루프
원인: Refresh token도 만료됨
해결:
/api/auth/refresh응답 확인- 401 응답 시 로그인 페이지로 리다이렉트
needsReauth: true플래그 확인
문제 3: CORS 에러
원인: 크로스 도메인 요청 시 쿠키 전송 실패
해결:
fetch('/api/protected', {
credentials: 'include' // 쿠키 포함
})
참고 파일
src/app/api/auth/login/route.ts- 로그인 APIsrc/app/api/auth/refresh/route.ts- 토큰 갱신 APIsrc/app/api/auth/check/route.ts- 인증 체크 APIsrc/app/api/auth/logout/route.ts- 로그아웃 APIsrc/middleware.ts- 인증 미들웨어src/lib/auth/token-refresh.ts- 토큰 갱신 유틸리티src/lib/api/client.ts- API 클라이언트 (자동 갱신)