우리 도메인끼리도 남남이다

우리 도메인끼리도 남남이다

Origin과 Site, 쿠키 Domain
2026년 5월 4일

🌐 우리 도메인끼리도 남남이다

Origin과 Site, 쿠키 Domain — 같은 회사인데 따로 노는 이유

가상의 상황

한 서비스를 프론트엔드와 백엔드로 나눠서 배포한다고 해보자. 프론트는 client.example.co.kr, 백엔드는 api.example.co.kr. 같은 회사 도메인 아래 호스트만 다르다.

클라이언트에서 백엔드 API를 부른다.

이 호출은 의외로 한 번에 동작하지 않는다. 쿠키가 함께 전송되지 않고, CORS 에러가 발생하고, 로컬 dev 환경에서는 또 다른 문제가 나타난다.

이전에 인증, 고를 게 많다에서 인증의 4가지 축을 정리했었다. 그때는 "웹은 HttpOnly 쿠키, 모바일은 Secure Store" 정도의 결론에서 멈췄다.

이번에는 쿠키를 선택했다는 가정하에 더 구체적인 내용을 정리해보려고 한다.

같은 회사 도메인이라도 출처는 다르다

client.example.co.krapi.example.co.kr는 같은 회사가 운영하는 같은 사이트로 보이지만, 브라우저는 둘을 다른 출처로 인식한다.

Origin과 Site는 다른 단위다

단위정의예시
Origin프로토콜 + 호스트 + 포트https://client.example.co.kr
Site프로토콜 + eTLD+1example.co.kr

eTLD+1은 사람이 직접 등록할 수 있는 도메인 단위다. .com, .co.kr 같은 공개 접미사(eTLD) 바로 아래 한 단계를 의미한다. naver.com, example.co.kr 같은 것이 eTLD+1에 해당한다.

이 기준을 두 도메인에 적용해보면 Origin은 다르지만(호스트가 다르므로) Site는 같다. 같은 사이트지만 다른 출처인 상태가 된다.

단위가 두 개인 이유

CORS는 Origin 단위로, SameSite 쿠키는 Site 단위로 동작한다. 같은 회사 도메인끼리도 어떤 정책은 막히고 어떤 정책은 통과하는 이유가 여기에 있다.

직접 호출하면 어떤 일이 일어나는가

client.example.co.kr 페이지에서 api.example.co.kr로 fetch를 보낸다고 해보자. 브라우저는 세 가지를 검증한다.

① CORS 검증 (Origin 기준)

client.example.co.krapi.example.co.kr는 다른 출처이므로 CORS 정책이 적용된다.

POST, PUT, DELETE 같은 mutation 요청 앞에는 OPTIONS Preflight 요청이 한 번 더 붙는다. "이런 요청 보내도 되나요?"를 먼저 물어보고, 백엔드가 허용하면 그제서야 본 요청이 나간다.

쿠키를 함께 전송하려면 백엔드가 응답 헤더에 Access-Control-Allow-Origin: https://client.example.co.krAccess-Control-Allow-Credentials: true를 포함해야 한다.

여기서 한 가지 주의할 점은 Access-Control-Allow-Origin에 와일드카드(*)를 사용할 수 없다는 것이다. 자격증명을 함께 보내는 요청에서 모든 출처를 허용해버리면, 어떤 사이트에서든 사용자의 쿠키로 API를 호출할 수 있게 되기 때문이다. 그래서 출처를 명시적으로 적도록 강제되어 있다.

② SameSite 쿠키 정책 (Site 기준)

쿠키의 SameSite 속성은 다른 사이트로 가는 요청에 쿠키를 함께 보낼지를 결정한다. 값은 세 가지다.

동작
Strict다른 사이트로 가는 요청에는 쿠키를 보내지 않음
Lax기본값. 일반 링크 이동에서는 보내지만, cross-site 백그라운드 요청(fetch, iframe 등)에서는 차단
None다른 사이트로 가는 요청에도 쿠키를 보냄. Secure 속성이 함께 필요

지금 상황에서 client.example.co.krapi.example.co.kr는 같은 사이트(example.co.kr)에 속한다. 그래서 어떤 SameSite 값이든 same-site 요청에는 쿠키가 정상적으로 전송된다.

만약 백엔드가 다른 회사 도메인이었다면 쿠키를 SameSite=None; Secure로 설정해야만 cross-site 요청에 쿠키가 전송된다. 같은 사이트로 묶여 있어 이 부담이 줄어든다.

③ 쿠키 Domain 매칭

세 번째로 확인할 부분은 쿠키의 Domain 속성이다.

서버가 응답으로 Set-Cookie를 보낼 때 Domain 속성을 어떻게 지정하느냐에 따라 쿠키가 적용되는 범위가 달라진다.

Domain 값효과
지정하지 않음 (host-only)발급한 호스트에만 묶임
Domain=api.example.co.kr위와 거의 동일
Domain=.example.co.kr*.example.co.kr 모든 서브도메인에 적용

이 값에 따라 다음 요청에 쿠키가 자동으로 첨부될지가 결정된다.

쿠키 Domain을 어떻게 지정할 것인가

쿠키 Domain 설정은 크게 두 가지 방향이 있다. 하나는 발급한 호스트에만 쿠키를 묶어두는 host-only 방식이고, 다른 하나는 부모 도메인을 지정해서 하위 서브도메인 전체에 공유하는 방식이다.

host-only 쿠키는 격리성이 높다. 쿠키가 발급된 호스트 밖으로 나가지 않으니, 다른 서브도메인에 보안 문제가 생겨도 직접 영향을 받지 않는다. 다만 여러 서브도메인이 같은 인증을 공유해야 한다면 별도의 처리가 필요해진다. 각자 따로 로그인을 시키거나, 토큰을 전달하는 메커니즘을 따로 만들어야 한다.

부모 도메인 쿠키는 반대다. Domain=.example.co.kr로 지정하면 *.example.co.kr 하위 서브도메인 전체에 자동으로 공유된다.

이렇게 하면 다음과 같은 상황에서 편하다.

  • 여러 서브도메인이 한 번의 로그인으로 인증을 공유 (SSO 비슷한 효과)
  • SSR 서버와 API 서버가 다른 서브도메인이어도 쿠키가 자연스럽게 전달됨
  • OAuth 콜백 redirect가 여러 서브도메인을 오갈 때도 안전

대신 격리성이 약해진다. 예를 들어 blog.example.co.kr에 XSS 취약점이 생기면, 공격자가 그 서브도메인에서 api.example.co.kr로 인증된 요청을 보낼 수 있다. HttpOnly 옵션으로 토큰을 직접 탈취하지는 못하더라도, 사용자의 권한을 도용할 수 있다는 뜻이다.

어느 쪽이 정답이라고 단정하기는 어렵다. 한 회사가 모든 서브도메인을 통제하고 보안 정책을 엄격히 관리한다면 부모 도메인 쿠키의 편의성이 크다. 반대로 화이트 라벨링처럼 외부 파트너에게 서브도메인을 내주는 구조라면, 통제 범위 밖에서 인증이 새어나갈 수 있어 host-only 쪽이 안전한 것 같다.

단, 이 글에서는 부모 도메인 쿠키를 사용하는 상황을 가정해 다른 부분을 더 살펴보았다.

dev 환경에서 또 다른 문제가 생긴다

prod 환경에서는 위 설정으로 잘 동작한다. 그런데 로컬 개발 환경에서는 또 다른 문제가 나타난다.

시나리오

  • 프론트: localhost:5173 (로컬 dev 서버)
  • 백엔드: https://dev-api.example.co.kr (원격 dev 백엔드)

localhost에서 dev-api.example.co.kr로 직접 호출한다고 가정해보자.

백엔드는 요청의 Origin 헤더를 보고 쿠키 Domain을 다르게 발급한다. 대략 이런 식의 분기가 들어간다.

클라이언트 Origin에 localhost가 포함되면 백엔드가 Domain=.localhost 쿠키를 발급한다.

그런데 그 쿠키가 거부된다

브라우저는 Set-Cookie를 받을 때 RFC 6265에 따라 검증한다.

응답을 보낸 서버가 쿠키 Domain과 domain-match가 되지 않으면 쿠키를 무시한다.

직접 호출한 경우를 따져보면, 응답을 보낸 서버는 dev-api.example.co.kr인데 쿠키 Domain은 .localhost다. dev-api.example.co.kr.localhost로 끝나지 않으니 매칭이 성립하지 않는다.

그래서 브라우저는 이 쿠키를 거부한다. 쿠키가 저장되지 않으니 다음 요청에 첨부할 것도 없고, 결과적으로 로그인이 동작하지 않는다.

문제를 푸는 몇 가지 방법

이 상황을 해결하는 방법은 여러 가지가 있다.

  1. 백엔드 쿠키 발급 로직 수정

localhost 분기에서 Domain 속성을 빼고 host-only 쿠키로 발급하는 방법. 도메인 매칭 문제는 사라지지만, 백엔드 코드를 건드려야 하고 prod/dev 분기를 관리해야 한다.

  1. CORS와 쿠키 정책 완화

백엔드가 localhost:5173을 허용하고 쿠키를 SameSite=None; Secure로 발급하는 방법. dev 설정을 prod와 분리해야 하고, "dev에서는 되는데 prod에서 안 되는" 차이가 생길 여지가 있다.

  1. hosts 파일 조작

/etc/hosts127.0.0.1 dev.example.co.kr을 추가해 로컬을 회사 도메인처럼 위장하는 방법. same-origin이 되어 모든 문제가 풀리지만, 팀원 모두가 동일하게 설정해야 하고 HTTPS는 로컬 인증서를 따로 발급해야 한다.

  1. dev 프록시

프론트 dev 서버가 백엔드로 가는 요청을 대신 전달하는 방법. Vite, Webpack 등 빌드 도구에 기본 기능으로 들어 있어 설정이 간단하고, 백엔드나 hosts 파일을 건드리지 않아도 된다.

이 글에서는 마지막 방법인 dev 프록시를 살펴보려고 한다.

프록시는 어떻게 동작하는가

브라우저 시점에서 보면 요청을 보낸 곳도, 응답을 받은 곳도 localhost:5173이다. dev-api.example.co.kr은 한 번도 본 적이 없다.

응답이 localhost에서 온 것처럼 보이기 때문에, .localhost 쿠키도 매칭이 성립해서 정상적으로 저장된다.

프록시는 누구를 가리는가

처음에는 프록시의 방향이 헷갈렸다. localhost를 백엔드처럼 위장하는 것인가 싶었는데, 실제로는 그 반대였다.

프록시는 백엔드를 localhost인 것처럼 보이게 만든다. 브라우저는 끝까지 localhost와만 통신한다고 인식한다.

프록시의 본질은 양쪽이 서로를 직접 보지 못하게 만드는 중간 다리에 있는 것 같다.

정리하면

같은 회사 도메인을 두 개로 나눠 운영할 때, 인증이 동작하려면 다음 세 가지가 동시에 맞아야 한다.

메커니즘정책어떻게 풀리나
Origin 기준CORS백엔드가 출처를 명시 허용
Site 기준SameSite 쿠키같은 사이트라 SameSite 정책에 막히지 않음
Domain 매칭쿠키 저장부모 도메인 또는 host-only 중 선택

그리고 dev 환경에서는 localhost와 원격 백엔드 사이의 도메인이 어긋나는 문제가 추가로 생긴다. 백엔드 수정, 설정 완화, hosts 조작, dev 프록시 등 여러 방법이 있고 그중 하나를 골라야 한다.

마치며

평소에는 "동작하니까 쓴다"에 가까운 태도였다. 한 번 코드를 들여다보고 나서야 동작시키기 위해 이렇게까지 풀어둔 것이라는 사실을 알게 됐다.

결국 동작하는 코드를 한 번 직접 들여다보는 것에서 진짜 이해/공부가 시작되는 것 같다 🥹