Files
sam-react-prod/sam-docs/frontend/v1/03-authentication.md
유병철 c309ac479f feat: [vehicle] 법인차량 관리 모듈 + MES 분석 보고서 + 프론트엔드 문서
- 법인차량 관리 3개 페이지 (차량등록, 운행일지, 정비이력)
- MES 데이터 정합성 분석 보고서 v1/v2
- sam-docs 프론트엔드 기술문서 v1 (9개 챕터)
- claudedocs 가이드/테스트URL 업데이트
2026-03-13 17:52:57 +09:00

4.1 KiB

인증 및 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 리다이렉트

토큰 갱신 중복 방지

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

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

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

// 클라이언트에서 프록시 호출
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

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

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

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

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 또는 프록시 사용