2025 . 오은

repo

Cookie, CORS, Site/Origin 완벽 정리

2025.11.14

🍪 Cookie, CORS, Site/Origin 총정리

1. TL;DR

example.com(프론트엔드)에서 api.example.com(백엔드)으로 인증 요청을 보낼 때:

  • ✅ Same-Site이지만 Cross-Origin입니다
  • ✅ 쿠키에 domain: ".example.com" 설정 필요
  • ✅ sameSite: "lax" 충분 (Cross-Site 아니므로)
  • ✅ 백엔드 CORS 설정 필수 (별개 정책!)
  • ✅ Apollo Client credentials: "include" 필수

핵심: 쿠키 정책(브라우저)과 CORS(서버) 둘 다 맞춰야 성공..


2. Site vs Origin 개념

📚 정의

  • Origin = Protocol + Domain + Port (모두 일치해야 Same-Origin)
  • Site = 루트 도메인(eTLD+1)이 같으면 Same-Site

🌳 관계도

Same-Site
├── Same-Origin
│   └── example.com → example.com
│       (protocol, domain, port 모두 일치)
│
└── Cross-Origin
    └── example.com → api.example.com
        (subdomain이 다름)

Cross-Site (무조건 Cross-Origin)
└── Cross-Origin
    └── example.com → google.com
        (완전히 다른 도메인)

논리 관계

IF Same-Site:
  ├─ Same-Origin 가능
  └─ Cross-Origin 가능

IF Cross-Site:
  └─ Cross-Origin 무조건

IF Same-Origin:
  └─ Same-Site 무조건

IF Cross-Origin:
  ├─ Same-Site 가능 (subdomain만 다를 때)
  └─ Cross-Site 가능 (완전히 다른 도메인)

실제 예시

FromToSiteOrigin
example.comexample.comSame-SiteSame-Origin
example.comapi.example.comSame-SiteCross-Origin
https://example.comhttp://example.comSame-SiteCross-Origin
example.comgoogle.comCross-SiteCross-Origin

3. 쿠키 Domain 옵션

핵심 원칙

쿠키에 domain 옵션을 명시하지 않으면, 쿠키는 **설정된 정확한 호스트(subdomain 포함)**에만 유효!!

설정별 동작

1️⃣ Same-Origin (domain 옵션 불필요)

// example.com → example.com
cookieStore.set('token', jwt, {
  // domain 생략 가능
  path: "/",
  sameSite: "strict", // 가장 엄격한 보안
  secure: true,
  httpOnly: true,
});

쿠키 전송 범위:

  • ✅ example.com → example.com

사용 사례: Next.js API Routes, 모놀리식 아키텍처


2️⃣ Same-Site/Cross-Origin (domain 옵션 필수!) ⭐

// example.com → api.example.com
cookieStore.set('token', jwt, {
  domain: ".example.com", // ⭐ 점(.) 필수! 모든 subdomain 공유
  path: "/",
  sameSite: "lax", // Same-Site이므로 충분
  secure: true,
  httpOnly: true,
  maxAge: 30 * 24 * 60 * 60, // 30일
});

쿠키 전송 범위:

  • ✅ example.com → example.com
  • ✅ example.com → api.example.com (전송됨!)
  • ✅ example.com → admin.example.com

사용 사례: 마이크로서비스, API 서버 분리, SSO


3️⃣ Cross-Site (점점 불가능해지는 중) !!

// example.com → external.com
cookieStore.set('token', jwt, {
  domain: ".example.com",
  sameSite: "none", // ⚠️ 필수!
  secure: true,      // ⚠️ 필수!
  httpOnly: true,
});

주의사항:

  • Chrome 등 주요 브라우저에서 3rd party 쿠키 차단 중
  • 2024년부터 기본적으로 차단
  • 대안: JWT를 Authorization 헤더로 전송

사용 사례: 외부 인증 제공자


4. 백엔드 CORS는 별개 정책!

두 가지 독립적인 보안 레이어

1️⃣ CORS (서버 측 정책) → 서버: "이 origin 요청 받을까?"
2️⃣ Cookie Policy (브라우저 정책) → 브라우저: "이 쿠키를 보낼까?"

둘 다 통과해야 성공임..!

성공 케이스 (모두 OK)

// 프론트엔드: example.com
// 백엔드: api.example.com

// 1️⃣ 쿠키 설정 (브라우저 정책)
cookieStore.set('token', jwt, {
  domain: ".example.com", // ✅
  sameSite: "lax",         // ✅
  secure: true,
  httpOnly: true,
});

// 2️⃣ Apollo Client 설정
const httpLink = createHttpLink({
  uri: 'https://api.example.com/v1/graphql',
  credentials: 'include', // ✅ 필수!
});

// 3️⃣ 백엔드 CORS 설정 (서버 정책)
// api.example.com 환경 변수
CORS_ORIGIN: "https://example.com" // ✅
// 또는 Hasura의 경우
HASURA_GRAPHQL_CORS_DOMAIN: "https://example.com"

결과: 🎉 요청 성공! 인증 성공!


실패 케이스 1: CORS 차단

// 1️⃣ 쿠키 설정: ✅ OK
cookieStore.set('token', jwt, {
  domain: ".example.com",
  sameSite: "lax",
});

// 2️⃣ CORS 설정: ❌ 없음
// api.example.com에 CORS 설정 안 함

브라우저 콘솔 에러:

Access to fetch at 'https://api.example.com/v1/graphql' 
from origin 'https://example.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present.

결과: 🔴 요청 자체가 차단됨


⚠️ 실패 케이스 2: 쿠키 정책 실패

// 1️⃣ 쿠키 설정: ❌ domain 없음
cookieStore.set('token', jwt, {
  // domain 없음 → example.com만
  sameSite: "lax",
});

// 2️⃣ CORS 설정: ✅ OK
HASURA_GRAPHQL_CORS_DOMAIN: "https://example.com"

Network 탭:

Request Headers:
  (Cookie 헤더 없음) ❌

Response:
  200 OK ✅
  { "x-hasura-role": "guest" } ⚠️ 인증 실패

결과: 요청은 성공하지만 인증 실패 ㅜㅜ


완전한 체크리스트

Cross-Origin 인증 요청이 성공하려면:

백엔드 (api.example.com)
  ✅ CORS 설정
    ├─ Access-Control-Allow-Origin: https://example.com
    ├─ Access-Control-Allow-Credentials: true
    ├─ Access-Control-Allow-Methods: POST, GET, OPTIONS
    └─ Access-Control-Allow-Headers: Content-Type, Authorization

프론트엔드 (example.com)
  ✅ 쿠키 설정
    ├─ domain: ".example.com"
    ├─ sameSite: "lax"
    ├─ secure: true
    └─ httpOnly: true
  
  ✅ HTTP Client 설정
    └─ credentials: "include" (fetch API)
    └─ credentials: "include" (Apollo Client)

하나라도 빠지면 실패입니다..


5. 교훈

  1. Site ≠ Origin

    • Same-Site: 루트 도메인만 같으면 OK
    • Same-Origin: Protocol + Domain + Port 모두 같아야 함
  2. 쿠키는 domain 옵션이 핵심

    • domain: ".example.com" → 모든 subdomain 공유
    • 옵션 없음 → 정확한 호스트만
  3. CORS는 별개 정책

    • 쿠키 정책(브라우저) ≠ CORS(서버)
    • 둘 다 맞춰야 성공!
  4. 실무에서는 Same-Site/Cross-Origin이 일반적

    • 프론트엔드와 API를 subdomain으로 분리
    • domain: ".example.com" + sameSite: "lax" + CORS 설정

⚡⚡⚡⚡ 명확히 이해하자..!

CORS, Site, Origin, 쿠키 정책은 각각 다른 보안 레이어!
개념을 명확히 이해하고, 각 레이어를 올바르게 설정해야 Cross-Origin 인증이 성공!


참고 자료

  • MDN - HTTP Cookies
  • MDN - CORS
  • MDN - Same-origin policy
  • web.dev - SameSite cookies explained