Files
sam-react-prod/claudedocs/auth/[REF] session-migration-frontend.md
byeongcheolryu 65a8510c0b fix: 품목기준관리 실시간 동기화 수정
- BOM 항목 추가/수정/삭제 시 섹션탭 즉시 반영
- 섹션 복제 시 UI 즉시 업데이트 (null vs undefined 이슈 해결)
- 항목 수정 기능 추가 (useTemplateManagement)
- 실시간 동기화 문서 추가

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-27 22:19:50 +09:00

580 lines
14 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 세션 기반 인증 전환 가이드 - 프론트엔드 (Next.js)
## 📋 개요
**목적**: 백엔드 세션 기반 인증에 맞춰 프론트엔드 수정
**주요 변경 사항**:
- ❌ JWT 토큰 저장 로직 제거
- ✅ 백엔드 세션 쿠키 전달 방식으로 변경
- ❌ 토큰 갱신 엔드포인트 제거
- ✅ 모든 API 호출에 `credentials: 'include'` 추가
---
## 🔍 현재 구조 분석
### 현재 파일 구조
```
src/
├── app/
│ └── api/
│ └── auth/
│ ├── login/route.ts # 백엔드 토큰 → 쿠키 저장
│ ├── logout/route.ts # 쿠키 삭제
│ ├── refresh/route.ts # ❌ 삭제 예정
│ └── check/route.ts # 쿠키 확인
├── lib/
│ └── auth/
│ └── token-refresh.ts # ❌ 삭제 예정
└── middleware.ts # 인증 체크
```
---
## 📝 백엔드 준비 대기 상황
### 백엔드에서 준비 중인 사항
1. **세션 드라이버 Redis 설정**
2. **인증 가드 세션으로 변경**
3. **로그인 API 응답 변경**:
```json
// 변경 전
{
"access_token": "eyJhbG...",
"refresh_token": "eyJhbG...",
"token_type": "bearer"
}
// 변경 후
{
"message": "Login successful",
"user": {...},
"tenant": {...}
}
// + Set-Cookie: laravel_session=abc123
```
4. **CORS 설정**: `supports_credentials: true`
5. **세션 하이재킹 감지 미들웨어**
6. **`/api/v1/auth/check` 엔드포인트 추가**
---
## 🛠️ 프론트엔드 변경 작업
### 1⃣ 로그인 API 수정
**파일**: `src/app/api/auth/login/route.ts`
**변경 사항**:
- ✅ `credentials: 'include'` 추가
- ✅ 백엔드 세션 쿠키를 클라이언트로 전달
- ❌ 토큰 저장 로직 제거
```typescript
// src/app/api/auth/login/route.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
/**
* 🔵 세션 기반 로그인 프록시
*
* 변경 사항:
* - 토큰 저장 로직 제거
* - 백엔드 세션 쿠키를 클라이언트로 전달
* - credentials: 'include' 추가
*/
interface BackendLoginResponse {
message: string;
user: {
id: number;
user_id: string;
name: string;
email: string;
phone: string;
};
tenant: {
id: number;
company_name: string;
business_num: string;
tenant_st_code: string;
other_tenants: unknown[];
};
menus: Array<{
id: number;
parent_id: number | null;
name: string;
url: string;
icon: string;
sort_order: number;
is_external: number;
external_url: string | null;
}>;
roles: Array<{
id: number;
name: string;
description: string;
}>;
}
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { user_id, user_pwd } = body;
if (!user_id || !user_pwd) {
return NextResponse.json(
{ error: 'User ID and password are required' },
{ status: 400 }
);
}
// ✅ 백엔드 세션 기반 로그인 호출
const backendResponse = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-API-KEY': process.env.NEXT_PUBLIC_API_KEY || '',
},
body: JSON.stringify({ user_id, user_pwd }),
credentials: 'include', // ✅ 세션 쿠키 수신
});
if (!backendResponse.ok) {
let errorMessage = 'Authentication failed';
if (backendResponse.status === 422) {
errorMessage = 'Invalid credentials provided';
} else if (backendResponse.status === 429) {
errorMessage = 'Too many login attempts. Please try again later';
} else if (backendResponse.status >= 500) {
errorMessage = 'Service temporarily unavailable';
}
return NextResponse.json(
{ error: errorMessage },
{ status: backendResponse.status === 422 ? 401 : backendResponse.status }
);
}
const data: BackendLoginResponse = await backendResponse.json();
// ✅ 백엔드 세션 쿠키를 클라이언트로 전달
const sessionCookie = backendResponse.headers.get('set-cookie');
const response = NextResponse.json({
message: data.message,
user: data.user,
tenant: data.tenant,
menus: data.menus,
roles: data.roles,
}, { status: 200 });
// ✅ 백엔드 세션 쿠키 전달
if (sessionCookie) {
response.headers.set('Set-Cookie', sessionCookie);
}
console.log('✅ Login successful - Session cookie set');
return response;
} catch (error) {
console.error('Login proxy error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
```
---
### 2⃣ 로그아웃 API 수정
**파일**: `src/app/api/auth/logout/route.ts`
**변경 사항**:
- ✅ `credentials: 'include'` 추가
- ✅ 세션 쿠키를 백엔드로 전달
- ❌ 수동 쿠키 삭제 로직 제거 (백엔드가 처리)
```typescript
// src/app/api/auth/logout/route.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
/**
* 🔵 세션 기반 로그아웃 프록시
*
* 변경 사항:
* - 백엔드에 세션 쿠키 전달하여 세션 파괴
* - 수동 쿠키 삭제 로직 제거
*/
export async function POST(request: NextRequest) {
try {
// ✅ 백엔드 로그아웃 호출 (세션 파괴)
const sessionCookie = request.headers.get('cookie');
await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/logout`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-API-KEY': process.env.NEXT_PUBLIC_API_KEY || '',
'Cookie': sessionCookie || '',
},
credentials: 'include', // ✅ 세션 쿠키 포함
});
console.log('✅ Logout complete - Session destroyed on backend');
return NextResponse.json(
{ message: 'Logged out successfully' },
{ status: 200 }
);
} catch (error) {
console.error('Logout proxy error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
```
---
### 3⃣ 인증 체크 API 수정
**파일**: `src/app/api/auth/check/route.ts`
**변경 사항**:
- ✅ `credentials: 'include'` 추가
- ✅ 백엔드 `/api/v1/auth/check` 호출
- ❌ 토큰 갱신 로직 제거
```typescript
// src/app/api/auth/check/route.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
/**
* 🔵 세션 기반 인증 상태 확인
*
* 변경 사항:
* - 백엔드 세션 검증 API 호출
* - 토큰 갱신 로직 제거 (세션은 자동 연장)
*/
export async function GET(request: NextRequest) {
try {
const sessionCookie = request.headers.get('cookie');
if (!sessionCookie) {
return NextResponse.json(
{ authenticated: false },
{ status: 200 }
);
}
// ✅ 백엔드 세션 검증
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/auth/check`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'X-API-KEY': process.env.NEXT_PUBLIC_API_KEY || '',
'Cookie': sessionCookie,
},
credentials: 'include', // ✅ 세션 쿠키 포함
});
if (response.ok) {
const data = await response.json();
return NextResponse.json(
{
authenticated: data.authenticated,
user: data.user || null
},
{ status: 200 }
);
}
return NextResponse.json(
{ authenticated: false },
{ status: 200 }
);
} catch (error) {
console.error('Auth check error:', error);
return NextResponse.json(
{ authenticated: false },
{ status: 200 }
);
}
}
```
---
### 4⃣ 미들웨어 수정
**파일**: `src/middleware.ts`
**변경 사항**:
- ✅ 세션 쿠키 확인 (`laravel_session`)
- ❌ 토큰 쿠키 확인 제거 (`access_token`, `refresh_token`)
```typescript
// src/middleware.ts (checkAuthentication 함수만)
/**
* 인증 체크 함수
* 세션 쿠키 기반으로 변경
*/
function checkAuthentication(request: NextRequest): {
isAuthenticated: boolean;
authMode: 'session' | 'api-key' | null;
} {
// ✅ Laravel 세션 쿠키 확인
const sessionCookie = request.cookies.get('laravel_session');
if (sessionCookie && sessionCookie.value) {
return { isAuthenticated: true, authMode: 'session' };
}
// API Key (API 호출용)
const apiKey = request.headers.get('x-api-key');
if (apiKey) {
return { isAuthenticated: true, authMode: 'api-key' };
}
return { isAuthenticated: false, authMode: null };
}
```
---
### 5⃣ 파일 삭제
**삭제할 파일**:
```bash
# ❌ 토큰 갱신 API (세션은 자동 연장)
rm src/app/api/auth/refresh/route.ts
# ❌ 토큰 갱신 유틸리티
rm src/lib/auth/token-refresh.ts
```
---
## 📋 변경 작업 체크리스트
### 필수 변경
- [ ] `src/app/api/auth/login/route.ts`
- [ ] `credentials: 'include'` 추가
- [ ] 백엔드 세션 쿠키 전달 로직 추가
- [ ] 토큰 저장 로직 제거 (151-174 라인)
- [ ] `src/app/api/auth/logout/route.ts`
- [ ] `credentials: 'include'` 추가
- [ ] 세션 쿠키를 백엔드로 전달
- [ ] 수동 쿠키 삭제 로직 제거 (52-68 라인)
- [ ] `src/app/api/auth/check/route.ts`
- [ ] `credentials: 'include'` 추가
- [ ] 백엔드 `/api/v1/auth/check` 호출
- [ ] 토큰 갱신 로직 제거 (51-102 라인)
- [ ] `src/middleware.ts`
- [ ] `laravel_session` 쿠키 확인으로 변경
- [ ] `access_token`, `refresh_token` 확인 제거 (132-136 라인)
- [ ] 파일 삭제
- [ ] `src/app/api/auth/refresh/route.ts`
- [ ] `src/lib/auth/token-refresh.ts`
### 클라이언트 컴포넌트 확인
- [ ] 모든 `fetch()` 호출에 `credentials: 'include'` 추가
- [ ] 토큰 관련 상태 관리 제거 (있다면)
- [ ] 로그인 후 리다이렉트 로직 확인
---
## 🧪 테스트 계획
### 백엔드 준비 완료 후 테스트
#### 1. 로그인 테스트
```typescript
// 브라우저 개발자 도구 → Network 탭
fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
user_id: 'test',
user_pwd: 'password'
}),
credentials: 'include' // ✅ 확인
});
// 응답 확인:
// 1. Set-Cookie: laravel_session=abc123...
// 2. Response Body: { message: "Login successful", user: {...} }
```
#### 2. 세션 쿠키 확인
```javascript
// 브라우저 개발자 도구 → Application → Cookies
// laravel_session 쿠키 존재 확인
document.cookie; // "laravel_session=abc123..."
```
#### 3. 인증 체크 테스트
```typescript
fetch('/api/auth/check', {
credentials: 'include'
});
// 응답: { authenticated: true, user: {...} }
```
#### 4. 로그아웃 테스트
```typescript
fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include'
});
// 확인:
// 1. laravel_session 쿠키 삭제됨
// 2. /api/auth/check 호출 시 authenticated: false
```
#### 5. 세션 하이재킹 감지 테스트
```bash
# 1. 로그인 (정상 IP)
# 2. 쿠키 복사
# 3. VPN 또는 다른 네트워크에서 접근 시도
# 4. 자동 차단 확인 (401 Unauthorized)
```
---
## 🚨 주의사항
### 1. CORS 에러 발생 시
**증상**:
```
Access to fetch at 'http://api.example.com/api/v1/login' from origin 'http://localhost:3000'
has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header
in the response is '' which must be 'true' when the request's credentials mode is 'include'.
```
**해결**: 백엔드 팀에 확인 요청
- `config/cors.php`에서 `supports_credentials: true` 설정
- `allowed_origins`에 프론트엔드 도메인 추가
- 와일드카드 `*` 사용 불가
### 2. 쿠키가 전송되지 않는 경우
**원인**:
- `credentials: 'include'` 누락
- HTTPS 환경에서 `Secure` 쿠키 설정
**확인**:
```typescript
// 모든 API 호출에 추가
fetch(url, {
credentials: 'include' // ✅ 필수!
});
```
### 3. 개발 환경 (localhost)
**개발 환경에서는 HTTPS 없이도 작동**:
- 백엔드 `.env`: `SESSION_SECURE_COOKIE=false`
- 프로덕션에서는 반드시 `true`
### 4. 세션 만료 시간
- 백엔드 설정: `SESSION_LIFETIME=120` (2시간)
- 사용자가 2시간 동안 활동 없으면 자동 로그아웃
- 활동 중에는 자동 연장
---
## 🔄 마이그레이션 단계
### 단계 1: 백엔드 준비 (백엔드 팀)
- [ ] Redis 세션 드라이버 설정
- [ ] 인증 가드 변경
- [ ] CORS 설정
- [ ] API 응답 변경
- [ ] 테스트 완료
### 단계 2: 프론트엔드 변경 (현재 팀)
- [ ] 로그인 API 수정
- [ ] 로그아웃 API 수정
- [ ] 인증 체크 API 수정
- [ ] 미들웨어 수정
- [ ] 토큰 관련 파일 삭제
### 단계 3: 통합 테스트
- [ ] 로그인/로그아웃 플로우
- [ ] 세션 유지 확인
- [ ] 세션 하이재킹 감지
- [ ] 동시 로그인 제한
### 단계 4: 배포
- [ ] 스테이징 환경 배포
- [ ] 프로덕션 배포
- [ ] 모니터링
---
## 📞 백엔드 팀 협업 포인트
### 확인 필요 사항
1. **세션 쿠키 이름**: `laravel_session` (확인 필요)
2. **CORS 도메인 화이트리스트**: 프론트엔드 도메인 추가 요청
3. **세션 만료 시간**: 2시간 적절한지 확인
4. **API 엔드포인트**:
- ✅ `/api/v1/login` (세션 생성)
- ✅ `/api/v1/logout` (세션 파괴)
- ✅ `/api/v1/auth/check` (세션 검증)
- ❌ `/api/v1/refresh` (삭제)
### 배포 전 확인
- [ ] 백엔드 배포 완료 확인
- [ ] API 응답 형식 변경 확인
- [ ] CORS 설정 적용 확인
- [ ] 세션 쿠키 전송 확인
---
## 📚 참고 자료
- [Next.js API Routes](https://nextjs.org/docs/api-routes/introduction)
- [MDN: Fetch API with credentials](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#sending_a_request_with_credentials_included)
- [MDN: HTTP Cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)
---
**작성일**: 2025-11-12
**작성자**: Claude Code
**버전**: 1.0
**상태**: ⏳ 백엔드 준비 대기 중