
앞선 글에서는 Cookie, Session, Token의 역할을 구분했다.
그중 Cookie는 브라우저가 값을 저장하고, 조건에 맞는 HTTP 요청에 자동으로 포함할 수 있는 메커니즘이다.
하지만 브라우저에는 Cookie만 저장되는 것은 아니다.
대표적으로 Cookie Store, localStorage, sessionStorage, IndexedDB, Cache Storage 같은 저장 공간이 있다.
이 가운데 Cookie가 다른 저장소와 구분되는 중요한 특징은 HTTP 요청과 직접 연결되어 있다는 점이다.
예를 들어 localStorage에 저장한 값은 JavaScript가 직접 읽고 필요한 경우 Request Header 등에 넣어야 한다.
반면 Cookie는 브라우저가 Host, Domain, Path, HTTPS 여부, SameSite 같은 조건을 확인하고 조건에 맞는 요청에 자동으로 포함할 수 있다.
이번 글에서는 브라우저가 Cookie를 어떻게 저장하고, 어떤 조건을 기준으로 HTTP 요청에 Cookie를 포함할지 결정하는지 살펴본다.
목차
- 브라우저는 Cookie를 별도의 Cookie Store에서 관리한다
- Cookie Store와 다른 브라우저 저장소의 차이
- 브라우저가 Cookie 전송 여부를 판단하는 과정 - Domain과 Path가 Cookie의 전송 범위를 결정한다
- Host-only Cookie
- Domain을 지정한 Cookie
- Path - Secure와 HttpOnly는 Cookie의 사용 방법을 제한한다
- Secure
- HttpOnly - SameSite는 Cross-site 요청에서 Cookie 전송 범위를 결정한다
- Site와 Origin의 차이
- SameSite Options
- Strict
- Lax
- None - Cookie Scope와 SameSite는 서로 다른 문제다
- Cookie 문제를 확인하는 순서
1. 브라우저는 Cookie를 별도의 Cookie Store에서 관리한다
서버는 HTTP 응답의 Set-Cookie Header를 통해 브라우저에 Cookie 저장을 요청할 수 있다.
HTTP/1.1 200 OK
Set-Cookie: session_id=abc123
브라우저가 이 Cookie를 받아들일 수 있는 조건을 만족하면 Cookie Store에 저장한다.
Cookie Store는 localStorage나 IndexedDB와는 별도로 관리되며, Cookie마다 값뿐 아니라 어떤 요청에 전송할 것인지 판단하기 위한 속성도 함께 저장한다.
브라우저 저장 공간 비교
| 저장소 | HTTP 요청 자동 전송 | 주요 용도 |
| Cookie Store | O | Session ID, 인증 Cookie, HTTP와 연결된 상태 |
| localStorage | X | JavaScript에서 직접 사용하는 지속 데이터 |
| sessionStorage | X | 현재 Tab 또는 Page Session 단위 데이터 |
| IndexedDB | X | 비교적 큰 구조화 데이터 |
| Cache Storage | X | Request / Response Cache |
Cookie의 주요 속성
| 속성 | 역할 |
| Name / Value | Cookie의 이름과 실제 값 |
| Domain / Host | 어떤 Host 범위에 Cookie를 전송할지 결정 |
| Path | 어떤 URL Path에 Cookie를 전송할지 결정 |
| Expires / Max-Age | Cookie의 만료 시점 또는 수명 결정 |
| Secure | HTTPS 요청에서만 Cookie를 전송하도록 제한 |
| HttpOnly | JavaScript에서 Cookie 값을 직접 읽지 못하도록 제한 |
| SameSite | Cross-site Context에서 Cookie 전송 여부를 제한 |
브라우저는 Cookie Store에 Cookie를 저장하고,
이후 HTTP 요청이 발생하면 브라우저는 Cookie Store에서 해당 요청에 사용할 수 있는 Cookie를 찾는다.
이때 Host와 Domain이 맞는지, Path가 범위에 포함되는지, Cookie가 만료되지 않았는지, Secure와 SameSite 조건을 만족하는지 등을 확인한다.
Cookie Store → 요청 Host/Domain 확인 → Path 확인 → Secure 확인 → SameSite 확인 → 조건을 만족한 Cookie 선택
조건을 만족하는 Cookie가 있으면 브라우저가 Cookie Header를 구성해 요청에 포함한다.
GET /profile HTTP/1.1
Host: app.example.com
Cookie: session_id=abc123
따라서 Cookie는 단순히 브라우저에 값을 저장하는 저장소가 아니라 저장된 값을 HTTP 요청의 조건과 연결해서 자동으로 전달하는 메커니즘이라고 볼 수 있다.
2. Domain과 Path가 Cookie의 전송 범위를 결정한다
Cookie가 Cookie Store에 존재한다고 해서 모든 HTTP 요청에 포함되는 것은 아니다.
먼저 현재 요청의 Host와 Path가 해당 Cookie의 범위에 포함되는지를 확인해야 한다.
Host-only Cookie
다음 응답을 보자.
Set-Cookie: session_id=abc123; Path=/
Domain 속성이 없다.
이 경우 Cookie는 응답을 보낸 Host에만 연결되는 Host-only Cookie가 된다.
예를 들어 이 Cookie를 auth.example.com의 응답에서 받았다면 Cookie는 auth.example.com에 연결된다.
같은 example.com 아래에 있더라도 app.example.com의 Cookie가 되는 것은 아니다.
auth.example.com → session_id 있음
app.example.com → 해당 host-only session_id 없음
이 차이는 Authentication Server와 Application을 서로 다른 Host로 구성할 때 특히 중요하다.
로그인 요청 자체가 성공했더라도 Cookie가 auth.example.com에만 저장되어 있다면 app.example.com의 요청에는 해당 Cookie가 포함되지 않는다.
Domain을 지정한 Cookie
서버가 다음과 같이 Domain을 지정할 수도 있다.
Set-Cookie: session_id=abc123; Domain=example.com; Path=/
이 경우 Cookie는 example.com과 그 하위 Domain에 적용될 수 있다.
예를 들어 app.example.com, auth.example.com, api.example.com 같은 Host에서 Cookie의 다른 조건까지 만족한다면 전송 대상이 될 수 있다.
| 구분 | Domain 속성 | 적용 범위 |
| Host-only Cookie | 없음 | Cookie를 설정한 Host에만 적용 |
| Domain Cookie | 있음 | 지정한 Domain과 그 하위 Domain에 적용 가능 |
Cookie를 여러 Subdomain에서 공유해야 한다는 이유만으로 무조건 넓은 Domain을 지정하는 것은 적절하지 않다.
Cookie의 범위가 넓어지면 더 많은 Host가 같은 Cookie의 영향을 받을 수 있기 때문에 필요한 범위만 사용하는 것이 좋다.
또 하나 중요한 점은 Cookie의 Host/Domain 범위는 Port로 구분되지 않는다는 것이다.
example.com:3000과 example.com:8000은 Origin은 다르지만 Cookie의 Host 기준에서는 같은 Host를 사용한다.
Path
Path는 같은 Host 안에서도 Cookie를 전송할 URL 범위를 제한한다.
Set-Cookie: auth_state=xyz; Path=/auth
이 Cookie는 /auth 경로 아래의 요청에 적용될 수 있다.
예를 들어 다음 요청은 범위에 포함된다.
- /auth
- /auth/login
- /auth/callback
반면 다음 경로는 /auth 범위에 포함되지 않는다.
- /api/users
- /profile
즉 Path는 같은 Host 안에서 어떤 요청 경로에 Cookie를 전송할지 정하는 범위다.
다만 Path는 JavaScript나 다른 코드로부터 Cookie를 안전하게 격리하기 위한 보안 경계로 사용하면 안 된다.
3. Secure와 HttpOnly는 Cookie의 사용 방법을 제한한다
인증에 사용하는 Cookie에는 Session ID나 Token처럼 민감한 값이 들어갈 수 있다.
이때 자주 함께 사용하는 속성이 Secure와 HttpOnly다.
Secure
다음 Cookie에는 Secure가 설정되어 있다.
Set-Cookie: session_id=abc123; Secure
Secure가 설정된 Cookie는 일반적으로 HTTPS를 통해 전송되는 요청에만 포함된다.
- HTTPS → Cookie 전송 가능
- HTTP → Secure Cookie 전송하지 않음
로그인 상태를 나타내는 Cookie가 암호화되지 않은 HTTP 연결을 통해 전달되는 것을 막기 위해 중요한 속성이다.
다만 Secure가 Cookie Value 자체를 암호화하는 것은 아니다.
Secure = Cookie 값 암호화가 아니라 Secure = HTTPS 연결을 통해서만 전송하도록 제한이라는 의미다.
실제 Network 구간의 암호화는 TLS가 담당한다.
HttpOnly
다음 Cookie에는 HttpOnly도 설정되어 있다.
Set-Cookie: session_id=abc123; Secure; HttpOnly
HttpOnly Cookie는 브라우저 내부에는 저장되지만 JavaScript의 document.cookie를 통해 직접 읽을 수 없다.
document.cookie
인증용 Session Cookie처럼 JavaScript에서 값을 직접 읽을 이유가 없다면 HttpOnly를 사용하는 것이 일반적이다.
다만 HttpOnly가 Cookie를 HTTP 요청에서 제거하는 것은 아니다.
조건을 만족하는 요청이라면 JavaScript의 fetch() 등으로 시작된 HTTP 요청에도 Cookie가 포함될 수 있다.
즉 HttpOnly가 제한하는 것은 JavaScript에서 Cookie 값을 직접 읽는 것이지 브라우저의 Cookie 전송 자체가 아니다.
| 속성 | 제하한하는 것 | 제한하지 않는 것 |
| Secure | HTTP 평문 연결을 통한 Cookie 전송 | Cookie Value 자체를 암호화하지는 않음 |
| HttpOnly | JavaScript에서 Cookie 값을 직접 읽는 것 | HTTP 요청에 Cookie가 자동으로 포함되는 것은 막지 않음 |
4. SameSite는 Cross-site 요청에서 Cookie 전송 범위를 결정한다
SameSite는 다른 Site에서 시작된 요청에 Cookie를 포함할 것인지를 결정하는 속성이다.
대표적인 값은 Strict, Lax, None이다.
Site와 Origin은 다른 개념이다
SameSite를 이해할 때 먼저 Site와 Origin을 구분할 필요가 있다.
Origin은 기본적으로 Scheme + Host + Port의 조합으로 구분한다.
반면 SameSite 판단에서 사용하는 Site는 Scheme과 registrable domain을 중심으로 판단하며 Port는 기준에 포함되지 않는다.
따라서 두 요청이 Cross-Origin이라고 해서 반드시 Cross-Site인 것은 아니다.
예를 들어 같은 Site 안에서도 Port가 다르면 Origin은 달라질 수 있다.
Strict
Set-Cookie: session_id=abc123; SameSite=Strict
Strict는 Cross-site Context에서 Cookie 전송을 가장 강하게 제한한다.
예를 들어 사용자가 other.com의 링크를 클릭해 example.com으로 이동한다고 하자.
이 요청은 다른 Site에서 시작된 Navigation이다.
SameSite=Strict Cookie는 이런 Cross-site 요청에는 포함되지 않는다.
반대로 이미 example.com 내부에서 발생하는 Same-site 요청에는 Cookie가 전송될 수 있다.
- Same-site Request → Cookie 전송 가능
- Cross-site Request → Cookie 전송 제한
Cross-site 이동을 지원할 필요가 없고 Cookie 전송 범위를 강하게 제한하고 싶은 경우 사용할 수 있다.
Lax
Set-Cookie: session_id=abc123; SameSite=Lax
Lax도 기본적으로 Cross-site 요청에서는 Cookie 전송을 제한한다.
하지만 사용자가 다른 Site의 링크를 클릭해 현재 Site로 이동하는 것과 같은 Top-level Navigation의 Safe Method에서는 Cookie를 허용할 수 있다.
대표적인 경우가 링크를 클릭해 발생하는 GET Navigation이다.
GET https://example.com/profile
Cookie: session_id=abc123
반대로 다른 Site에서 실행한 fetch(), iframe, 이미지 요청 같은 Cross-site Subresource Request에는 일반적으로 Lax Cookie가 포함되지 않는다.
- Same-site Request → Cookie 전송 가능
- Cross-site Top-level Navigation + Safe Method → Cookie 전송 가능
- Cross-site fetch / iframe / image 등 → Cookie 전송 제한
Strict보다 일반적인 웹 탐색을 허용하면서 Cross-site Request를 상당 부분 제한할 수 있다는 특징이 있다.
None
Set-Cookie: session_id=abc123; SameSite=None; Secure
None은 Same-site뿐 아니라 Cross-site Context에서도 Cookie를 사용할 수 있도록 허용한다.
다만 SameSite=None Cookie는 Secure를 함께 사용해야 한다.
따라서 일반적으로 HTTPS 환경에서 사용한다.
예를 들어 app-a.com에서 auth-b.com으로 Cross-site 요청을 보내고, auth-b.com의 Cookie를 함께 사용해야 한다면 SameSite=None; Secure가 필요할 수 있다.
하지만 SameSite=None이라고 해서 모든 Cross-site 요청에 Cookie가 무조건 포함되는 것은 아니다.
Domain, Path, Secure 같은 다른 Cookie 조건도 모두 만족해야 한다.
또한 JavaScript에서 Cross-Origin fetch()를 사용하는 경우에는 Fetch의 Credentials 설정도 함께 고려해야 한다.
fetch("https://auth-b.com/session", {
credentials: "include"
});
서버의 CORS 설정과 브라우저의 Third-party Cookie 정책에 따라서도 추가 제약을 받을 수 있다.
| SameSite | Smate-site 요청 | Cross-site Top-level Navigation | Cross-site fetch / iframe / image 등 |
| Strict | O | X | X |
| Lax | O | Safe Method의 Top-level Navigation은 O | X |
| None | O | O | O |
핵심은 SameSite가 Cookie를 Cookie Store에 저장할 수 있는지를 결정하는 속성이 아니라, 이미 저장된 Cookie를 어떤 Site Context의 요청에 실을 수 있는지를 결정하는 속성이라는 점이다.
5. Cookie Scope와 SameSite는 서로 다른 문제다
Cookie 문제를 디버깅할 때 가장 자주 혼동하는 부분이 Cookie Scope와 SameSite다.
예를 들어 Browser가 다음 두 Host를 사용한다고 하자.
- Frontend:
app.example.com - Authentication Server:
auth.example.com
사용자가 auth.example.com에서 로그인하고 다음 Cookie를 받았다고 하자.
Set-Cookie: session_id=abc123; Path=/; Secure; HttpOnly
Domain을 지정하지 않았으므로 이 Cookie는 auth.example.com의 Host-only Cookie다.
따라서 app.example.com으로 이동했을 때 같은 Cookie가 그 Host의 요청에 자동으로 사용되는 것은 아니다.
이 상황을 보고 바로 SameSite 문제라고 판단하면 원인을 잘못 찾을 수 있다.
현재 요청 Host의 Scope에 해당 Cookie 자체가 없다면 SameSite를 검사하기 이전에 이미 전송 대상에서 제외되기 때문이다.
Cookie 문제를 확인하는 순서
Cookie가 요청에 포함되지 않을 때는 다음 순서로 확인하면 원인을 분리하기 쉽다.
- 서버가
Set-Cookie를 보냈고 브라우저가 Cookie를 실제로 저장했는가 - Cookie가 어느 Host / Domain에 저장되어 있는가
- 현재 Request Path가 Cookie의 Path 범위에 포함되는가
- Secure Cookie라면 현재 요청이 HTTPS 조건을 만족하는가
- SameSite 정책상 현재 요청 Context에서 Cookie 전송이 허용되는가
- Cross-Origin
fetch()라면 Credentials 설정이 올바른가 - 브라우저의 Third-party Cookie 정책 등에 의해 추가로 차단되지 않았는가
- 최종적으로 실제 Request의
CookieHeader에 값이 포함되었는가
즉 다음 두 문제는 서로 구분해야 한다.
- 현재 Request가 Cookie의 Host / Domain / Path 범위에 들어오지 않음 → Cookie Scope 문제
- Cookie Scope는 맞지만 Cross-site Context 때문에 전송되지 않음 → SameSite 문제
예를 들어 auth.example.com에서 발급한 Host-only Cookie를 app.example.com에서 사용할 수 없는 문제는 SameSite=None으로 변경한다고 해결되지 않는다.
반대로 Host, Domain, Path는 모두 맞는데 Cross-site 요청에서만 Cookie가 빠진다면 그때 SameSite와 Cross-site Request 조건을 확인해야 한다.
결국 브라우저가 Cookie를 요청에 포함하는 과정을 전체 흐름으로 보면 다음과 같다.
Set-Cookie → Cookie Store →
Host / Domain → Path →
Secure →
SameSite →
Request Context → Cookie Header
Cookie는 단순히 브라우저에 저장된 문자열이 아니다.
브라우저가 Cookie Store에 값과 Scope, Security Attribute를 함께 저장하고 요청이 발생할 때마다 조건을 평가해 전송 여부를 결정하는 HTTP 메커니즘이다.
이 구조를 이해하면
- 로그인은 성공했지만 특정 Host에서 Session이 보이지 않는 문제
- HTTP에서는 Cookie가 전송되지 않는 문제
- Cross-site 요청에서만 Cookie가 빠지는 문제
등을 서로 다른 원인으로 구분해서 추적할 수 있다.
마무리
이번 글에서 살펴본 Cookie 전송 조건은 다음 흐름으로 정리할 수 있다.
Cookie Store → Host / Domain → Path → Secure / HttpOnly → SameSite → 실제 Request
각 속성은 서로 다른 문제를 해결한다.
- Host / Domain: 어느 Host 범위에서 사용할 것인가
- Path: 어느 URL Path에서 사용할 것인가
- Secure: HTTPS에서만 전송할 것인가
- HttpOnly: JavaScript에서 직접 읽을 수 있게 할 것인가
- SameSite: Cross-site Context에서 전송할 것인가
Cookie 문제를 해결할 때는 특정 속성을 무작정 변경하기보다 브라우저가 어떤 단계에서 해당 Cookie를 전송 대상에서 제외했는지를 순서대로 확인하는 것이 중요하다.
'보안' 카테고리의 다른 글
| JWT와 JWKS — Token을 서명하고 검증하는 방법 (0) | 2026.09.27 |
|---|---|
| 공개키와 개인키 — 암호화와 디지털 서명의 원리 (0) | 2026.09.27 |
| Token 기반 인증 이해하기 — Bearer, Access·Refresh Token (0) | 2026.09.26 |
| Session의 동작 원리 — Session ID와 서버 측 상태 관리 (1) | 2026.09.26 |
| 웹 인증의 기본 — Cookie / Session / Token (0) | 2026.09.25 |