504 lines
13 KiB
Markdown
504 lines
13 KiB
Markdown
|
|
# 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를 기준으로 개발**하면 다른 브라우저에서도 작동합니다!
|