# 인증 및 API 통신

## 인증 아키텍처

SAM ERP는 **HttpOnly Cookie 기반 인증**을 사용합니다. JavaScript에서 토큰을 직접 접근할 수 없으므로, 모든 인증 API 호출은 **Next.js API Proxy**를 통해 처리됩니다.

```
클라이언트 (브라우저)
  │
  ├── Server Action 호출 (useEffect에서)
  │   └── serverFetch() → 서버에서 쿠키 읽기 → 백엔드 API 호출
  │
  └── 프록시 API 호출 (fetch('/api/proxy/...'))
      └── API Proxy Route → 서버에서 쿠키 읽기 → 백엔드 API 호출
```

## 쿠키 구조

| 쿠키명 | HttpOnly | 용도 | Max-Age |
|--------|:--------:|------|---------|
| `access_token` | O | API 인증 토큰 | 2시간 |
| `refresh_token` | O | 토큰 갱신용 | 7일 |
| `is_authenticated` | X | 클라이언트 인증 상태 확인 | 2시간 |

- `access_token`, `refresh_token`: HttpOnly → JavaScript 접근 불가 (XSS 방지)
- `is_authenticated`: non-HttpOnly → 클라이언트에서 인증 상태 확인 가능 (FCM 등)
- 프로덕션: `Secure` 플래그 활성화 (HTTPS만)
- `SameSite=Lax`: CSRF 방지

## 인증 흐름

### 로그인

```
1. 사용자 → /api/auth/login (POST)
2. 백엔드 → access_token + refresh_token 반환
3. API Route → Set-Cookie (HttpOnly) 설정
4. 클라이언트 → /(protected)/dashboard 리다이렉트
```

### API 요청 (Server Action)

```
1. 클라이언트 → Server Action 호출
2. serverFetch() → 쿠키에서 access_token 읽기
3. authenticatedFetch() → Authorization 헤더에 토큰 추가
4. 백엔드 API 호출 → 응답 반환
```

### 토큰 만료 시 (401 자동 갱신)

```
1. API 요청 → 401 응답 수신
2. authenticatedFetch() → refresh_token으로 갱신 요청
3. 새 토큰 수신 → 쿠키 업데이트
4. 원래 요청 재시도 → 성공
5. 갱신 실패 → 쿠키 삭제 → /login 리다이렉트
```

### 토큰 갱신 중복 방지

```typescript
// globalThis 레벨 캐싱 (5초)
// 여러 요청이 동시에 401을 받아도 refresh는 1회만 실행
// 진행 중인 refresh Promise를 공유하여 대기
```

## API 프록시 (`/api/proxy/[...path]`)

클라이언트에서 직접 백엔드 API를 호출해야 하는 경우 프록시 사용:

```typescript
// 클라이언트에서 프록시 호출
const response = await fetch('/api/proxy/item-master/init');
const data = await response.json();
```

프록시 내부 동작:
1. HttpOnly 쿠키에서 `access_token` 읽기
2. 백엔드 URL 구성 (`/api/proxy/*` → 백엔드 `/*`)
3. `Authorization: Bearer {token}` 헤더 추가
4. 요청 전달 → 응답 반환
5. 401 시 자동 토큰 갱신 후 재시도
6. 새 토큰 → Set-Cookie 헤더로 클라이언트에 전달

## 인증 보호

### Protected Layout

```tsx
// (protected)/layout.tsx
export default function ProtectedLayout({ children }) {
  // 인증 가드 (뒤로가기 캐시 감지)
  useAuthGuard();

  return (
    <RootProvider>
      <ApiErrorProvider>     {/* 401 에러 자동 처리 */}
        <FCMProvider>        {/* 푸시 알림 */}
          <AuthenticatedLayout>
            <PermissionGate>   {/* 권한 기반 접근 제어 */}
              {children}
            </PermissionGate>
          </AuthenticatedLayout>
        </FCMProvider>
      </ApiErrorProvider>
    </RootProvider>
  );
}
```

### 인증 상태 확인 (클라이언트)

```typescript
import { hasAuthToken } from '@/lib/api/auth-headers';

// is_authenticated 쿠키 확인 (non-HttpOnly)
if (hasAuthToken()) {
  // 인증됨
}
```

## 로그아웃

완전한 로그아웃 절차:
1. Zustand 스토어 초기화 (useAuthStore, useMasterDataStore, useItemMasterStore)
2. sessionStorage 캐시 삭제 (page_config_*, mes-*)
3. localStorage 사용자 데이터 삭제
4. FCM 토큰 해제 (Capacitor 환경)
5. 서버 로그아웃 API 호출
6. /login 리다이렉트

## 주의사항

- **Server Component에서 쿠키 수정 불가** → Client Component 사용 필수
- **`alert()`, `confirm()`, `prompt()` 사용 금지** → Radix UI Dialog 또는 `toast` 사용
- **API 직접 호출 금지** → 반드시 Server Action 또는 프록시 사용
