# Safari 쿠키 호환성 및 크로스 브라우저 가이드

## 📋 목차
1. [문제 상황](#문제-상황)
2. [원인 분석](#원인-분석)
3. [해결 방법](#해결-방법)
4. [수정된 파일](#수정된-파일)
5. [크로스 브라우저 개발 가이드라인](#크로스-브라우저-개발-가이드라인)
6. [테스트 체크리스트](#테스트-체크리스트)

---

## 문제 상황

### Safari에서 발생한 인증 문제
- **로그인**: 성공했으나 대시보드로 이동 불가 ({"error":"Not authenticated"})
- **로그아웃**: 로그아웃 버튼 클릭 시 정상 동작하지 않음
- **크롬/파이어폭스**: 정상 작동

### 증상
```bash
# Safari 브라우저
✅ 로그인 API 호출 성공 (200 OK)
❌ 대시보드 접근 실패 (401 Unauthorized)
❌ 쿠키가 저장되지 않음

# Chrome/Firefox 브라우저
✅ 모든 기능 정상 작동
```

---

## 원인 분석

### Safari의 엄격한 쿠키 정책

Safari는 다른 브라우저보다 **쿠키 보안 정책이 엄격**합니다:

#### 1. Secure 속성 제한
```typescript
// ❌ Safari에서 작동하지 않음 (HTTP 환경)
const cookie = 'access_token=xxx; HttpOnly; Secure; SameSite=Strict';

// Safari 로직:
// - HTTP (localhost:3000) + Secure 속성 = 쿠키 저장 거부
// - HTTPS만 Secure 쿠키 허용
```

Chrome/Firefox는 `localhost`에서 `Secure` 속성을 허용하지만, **Safari는 허용하지 않습니다**.

#### 2. SameSite=Strict의 제약
```typescript
// SameSite=Strict: 모든 크로스 사이트 요청에서 쿠키 차단
// - 너무 엄격하여 일부 정상적인 요청도 차단될 수 있음

// SameSite=Lax: CSRF 보호 + 유연성
// - GET 요청과 top-level navigation에서는 쿠키 전송 허용
// - 대부분의 웹 애플리케이션에 적합
```

#### 3. 쿠키 삭제 시 속성 불일치
Safari는 쿠키를 삭제할 때 **설정할 때와 정확히 동일한 속성**을 요구합니다:

```typescript
// ❌ Safari에서 쿠키 삭제 실패
// 설정: HttpOnly + SameSite=Lax (Secure 없음)
// 삭제: HttpOnly + Secure + SameSite=Strict

// ✅ Safari에서 쿠키 삭제 성공
// 설정: HttpOnly + SameSite=Lax (Secure 없음)
// 삭제: HttpOnly + SameSite=Lax (Secure 없음)
```

---

## 해결 방법

### 핵심 원칙: 환경별 조건부 쿠키 설정

```typescript
// 1. 환경 감지
const isProduction = process.env.NODE_ENV === 'production';

// 2. 조건부 Secure 속성
const cookie = [
  'access_token=xxx',
  'HttpOnly',                                    // ✅ 항상 유지 (XSS 보호)
  ...(isProduction ? ['Secure'] : []),           // ✅ HTTPS에서만 적용
  'SameSite=Lax',                                // ✅ CSRF 보호 + 호환성
  'Path=/',
  'Max-Age=7200',
].join('; ');
```

### 환경별 쿠키 속성

| 환경 | Secure | SameSite | HttpOnly | 설명 |
|------|--------|----------|----------|------|
| **Development** (HTTP) | ❌ 없음 | Lax | ✅ 있음 | Safari 호환성 |
| **Production** (HTTPS) | ✅ 있음 | Lax | ✅ 있음 | 완전한 보안 |

---

## 수정된 파일

### 1. `src/app/api/auth/login/route.ts`

**수정 위치**: 150-170 라인

```typescript
// ❌ 기존 코드 (Safari 비호환)
const accessTokenCookie = [
  `access_token=${data.access_token}`,
  'HttpOnly',
  'Secure',              // 개발 환경에서 문제 발생
  'SameSite=Strict',     // 너무 엄격
  'Path=/',
  `Max-Age=${data.expires_in || 7200}`,
].join('; ');
```

```typescript
// ✅ 수정 코드 (Safari 호환)
const isProduction = process.env.NODE_ENV === 'production';

const accessTokenCookie = [
  `access_token=${data.access_token}`,
  'HttpOnly',              // ✅ JavaScript cannot access (XSS 보호)
  ...(isProduction ? ['Secure'] : []), // ✅ HTTPS only in production
  'SameSite=Lax',          // ✅ CSRF protection (Lax for compatibility)
  'Path=/',
  `Max-Age=${data.expires_in || 7200}`,
].join('; ');

const refreshTokenCookie = [
  `refresh_token=${data.refresh_token}`,
  'HttpOnly',
  ...(isProduction ? ['Secure'] : []),
  'SameSite=Lax',
  'Path=/',
  'Max-Age=604800',        // 7 days
].join('; ');
```

**변경 사항**:
- ✅ `Secure` 속성을 환경에 따라 조건부 적용
- ✅ `SameSite`를 `Strict`에서 `Lax`로 변경
- ✅ `refresh_token`도 동일하게 적용

---

### 2. `src/app/api/auth/check/route.ts`

**수정 위치**: 75-95 라인 (토큰 갱신 시)

```typescript
// ✅ 수정 코드
if (refreshResponse.ok) {
  const data = await refreshResponse.json();

  // Safari compatibility: Secure only in production
  const isProduction = process.env.NODE_ENV === 'production';

  const accessTokenCookie = [
    `access_token=${data.access_token}`,
    'HttpOnly',
    ...(isProduction ? ['Secure'] : []),
    'SameSite=Lax',
    'Path=/',
    `Max-Age=${data.expires_in || 7200}`,
  ].join('; ');

  const refreshTokenCookie = [
    `refresh_token=${data.refresh_token}`,
    'HttpOnly',
    ...(isProduction ? ['Secure'] : []),
    'SameSite=Lax',
    'Path=/',
    'Max-Age=604800',
  ].join('; ');

  // ... 쿠키 설정
}
```

**변경 사항**:
- ✅ 토큰 갱신 시에도 동일한 쿠키 설정 적용
- ✅ login/route.ts와 일관성 유지

---

### 3. `src/app/api/auth/logout/route.ts`

**수정 위치**: 52-71 라인 (쿠키 삭제)

```typescript
// ❌ 기존 코드 (Safari에서 쿠키 삭제 실패)
const clearAccessToken = [
  'access_token=',
  'HttpOnly',
  'Secure',           // 설정 시와 속성 불일치
  'SameSite=Strict',  // 설정 시와 속성 불일치
  'Path=/',
  'Max-Age=0',
].join('; ');
```

```typescript
// ✅ 수정 코드 (Safari에서 쿠키 삭제 성공)
// Safari compatibility: Must use same attributes as when setting cookies
const isProduction = process.env.NODE_ENV === 'production';

const clearAccessToken = [
  'access_token=',
  'HttpOnly',
  ...(isProduction ? ['Secure'] : []), // ✅ login과 동일
  'SameSite=Lax',                      // ✅ login과 동일
  'Path=/',
  'Max-Age=0',  // Delete immediately
].join('; ');

const clearRefreshToken = [
  'refresh_token=',
  'HttpOnly',
  ...(isProduction ? ['Secure'] : []),
  'SameSite=Lax',
  'Path=/',
  'Max-Age=0',
].join('; ');
```

**변경 사항**:
- ✅ 쿠키 삭제 시 설정 시와 **정확히 동일한 속성** 사용
- ✅ Safari의 엄격한 쿠키 삭제 정책 대응

---

## 크로스 브라우저 개발 가이드라인

### 필수 테스트 브라우저

모든 브라우저 관련 기능 개발 시 **다음 브라우저에서 반드시 테스트**:

| 브라우저 | 우선순위 | 주요 특징 | 테스트 환경 |
|---------|---------|----------|------------|
| **Chrome** | 🔴 High | 가장 관대한 정책 | macOS/Windows |
| **Safari** | 🔴 High | 가장 엄격한 정책 | macOS/iOS |
| **Firefox** | 🟡 Medium | 중간 수준 정책 | macOS/Windows |
| **Edge** | 🟢 Low | Chrome 기반 | Windows |

**개발 우선순위**: Safari 기준으로 개발하면 다른 브라우저에서도 작동합니다.

---

### 쿠키 관련 개발 원칙

#### 1. 환경별 조건부 설정
```typescript
// ✅ 항상 환경 체크
const isProduction = process.env.NODE_ENV === 'production';
const isSecure = isProduction; // HTTPS 여부

// ✅ Secure 속성은 항상 조건부로
...(isSecure ? ['Secure'] : [])
```

#### 2. HttpOnly는 항상 유지
```typescript
// ✅ XSS 공격 방지를 위해 HttpOnly는 항상 포함
'HttpOnly',  // 절대 제거하지 말 것
```

#### 3. SameSite는 Lax 권장
```typescript
// ✅ CSRF 보호 + 유연성
'SameSite=Lax',  // 대부분의 웹 앱에 적합

// ⚠️ Strict는 너무 엄격
'SameSite=Strict',  // 특별한 이유가 있을 때만 사용
```

#### 4. 쿠키 삭제 시 속성 일치
```typescript
// ✅ 설정할 때와 삭제할 때 속성이 정확히 일치해야 함
const setCookie = 'token=xxx; HttpOnly; SameSite=Lax';
const deleteCookie = 'token=; HttpOnly; SameSite=Lax; Max-Age=0';
```

---

### 로컬스토리지 vs 쿠키 선택 가이드

| 저장소 | 용도 | 보안 | Safari 호환성 |
|--------|------|------|---------------|
| **HttpOnly Cookie** | 인증 토큰 | ✅ 높음 (XSS 방지) | ✅ 조건부 설정 필요 |
| **LocalStorage** | 사용자 정보, 설정 | ⚠️ 낮음 (XSS 취약) | ✅ 호환성 좋음 |

**원칙**: 민감한 데이터(토큰)는 HttpOnly 쿠키, 일반 데이터는 LocalStorage

---

### Safari 개발 시 주의사항

#### 1. 쿠키 관련
- ✅ HTTP 환경에서 `Secure` 속성 제거
- ✅ 쿠키 설정과 삭제 시 속성 일치
- ✅ `SameSite=Lax` 사용 권장

#### 2. 네트워크 요청
```typescript
// ✅ Safari는 credentials 설정에 민감
fetch('/api/auth/check', {
  method: 'GET',
  credentials: 'include',  // Safari에서 쿠키 전송 필수
});
```

#### 3. 로컬스토리지
```typescript
// ✅ Safari Private Mode에서 localStorage 제한
try {
  localStorage.setItem('key', 'value');
} catch (error) {
  // Safari Private Mode 대응
  console.warn('LocalStorage unavailable:', error);
}
```

#### 4. 날짜/시간
```typescript
// ❌ Safari에서 파싱 실패 가능
new Date('2024-01-01 12:00:00');

// ✅ ISO 8601 형식 사용
new Date('2024-01-01T12:00:00Z');
```

---

### 크로스 브라우저 테스트 도구

#### 개발 환경 테스트
```bash
# Chrome
open -a "Google Chrome" http://localhost:3000

# Safari
open -a Safari http://localhost:3000

# Firefox
open -a Firefox http://localhost:3000
```

#### 개발자 도구 활용
```javascript
// Safari: Develop → Show Web Inspector → Storage
// Chrome: DevTools → Application → Cookies
// Firefox: DevTools → Storage → Cookies

// 쿠키 확인 사항:
// - Name: access_token, refresh_token
// - HttpOnly: ✅ 체크
// - Secure: 환경에 따라 조건부
// - SameSite: Lax
```

---

## 테스트 체크리스트

### 로그인 기능 테스트

#### Chrome
- [ ] 로그인 성공
- [ ] 대시보드 접근 가능
- [ ] 쿠키 저장 확인 (DevTools → Application → Cookies)
- [ ] HttpOnly 속성 확인
- [ ] 로그아웃 성공
- [ ] 쿠키 삭제 확인

#### Safari
- [ ] 로그인 성공
- [ ] 대시보드 접근 가능
- [ ] 쿠키 저장 확인 (Web Inspector → Storage → Cookies)
- [ ] HttpOnly 속성 확인
- [ ] Secure 속성 **없음** 확인 (개발 환경)
- [ ] 로그아웃 성공
- [ ] 쿠키 삭제 확인

#### Firefox (선택)
- [ ] 로그인 성공
- [ ] 대시보드 접근 가능
- [ ] 쿠키 저장 확인
- [ ] 로그아웃 성공

---

### 인증 상태 확인 테스트

#### 시나리오 1: 페이지 새로고침
- [ ] Chrome: 로그인 상태 유지
- [ ] Safari: 로그인 상태 유지
- [ ] Firefox: 로그인 상태 유지

#### 시나리오 2: 브라우저 재시작
- [ ] Chrome: 로그인 상태 유지 (Remember me)
- [ ] Safari: 로그인 상태 유지
- [ ] Firefox: 로그인 상태 유지

#### 시나리오 3: 토큰 만료
- [ ] Chrome: 자동 토큰 갱신
- [ ] Safari: 자동 토큰 갱신
- [ ] Firefox: 자동 토큰 갱신

---

### 프로덕션 배포 전 체크리스트

#### 환경 설정
- [ ] `NODE_ENV=production` 설정 확인
- [ ] HTTPS 인증서 설정 완료
- [ ] 환경 변수 `.env.production` 확인

#### 쿠키 설정 확인
- [ ] Production 환경에서 `Secure` 속성 포함 확인
- [ ] `HttpOnly` 속성 유지 확인
- [ ] `SameSite=Lax` 설정 확인
- [ ] `Max-Age` 적절히 설정 (access: 2h, refresh: 7d)

#### 브라우저 테스트 (HTTPS)
- [ ] Chrome: 로그인/로그아웃 정상
- [ ] Safari: 로그인/로그아웃 정상
- [ ] Firefox: 로그인/로그아웃 정상
- [ ] Safari iOS: 모바일 테스트

---

## 문제 해결 가이드

### 쿠키가 저장되지 않는 경우

#### 1. Safari 개발 환경
```typescript
// 체크 포인트:
// ✅ Secure 속성이 조건부로 설정되어 있는가?
...(isProduction ? ['Secure'] : [])

// ✅ SameSite가 Lax인가?
'SameSite=Lax'

// ✅ HttpOnly는 포함되어 있는가?
'HttpOnly'
```

#### 2. Safari Private Mode
Safari Private Mode에서는 일부 쿠키가 제한될 수 있습니다.
→ 일반 모드에서 테스트하세요.

#### 3. 쿠키 도메인 설정
```typescript
// ✅ localhost에서는 Domain 속성 생략
// ❌ 'Domain=localhost' (불필요)
```

---

### 쿠키가 삭제되지 않는 경우

#### Safari 로그아웃 문제
```typescript
// ❌ 설정 시와 삭제 시 속성 불일치
// 설정: HttpOnly + SameSite=Lax
// 삭제: HttpOnly + Secure + SameSite=Strict

// ✅ 설정 시와 삭제 시 속성 일치
const isProduction = process.env.NODE_ENV === 'production';
const cookie = [
  'token=',
  'HttpOnly',
  ...(isProduction ? ['Secure'] : []),  // 일치
  'SameSite=Lax',                       // 일치
  'Max-Age=0',
].join('; ');
```

---

## 관련 문서

- [MDN - HTTP Cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)
- [MDN - SameSite Cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite)
- [Safari Cookie Policy](https://webkit.org/blog/10218/full-third-party-cookie-blocking-and-more/)

---

## 업데이트 히스토리

| 날짜 | 내용 | 작성자 |
|------|------|--------|
| 2024-XX-XX | Safari 쿠키 호환성 문서 작성 | Claude |

---

**📌 기억하세요**: 브라우저 관련 기능 개발 시 **Safari를 기준으로 개발**하면 다른 브라우저에서도 작동합니다!