๐ช 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 ๊ฐ๋ฅ (์์ ํ ๋ค๋ฅธ ๋๋ฉ์ธ)
์ค์ ์์
| From | To | Site | Origin |
|---|---|---|---|
example.com |
example.com |
Same-Site | Same-Origin |
example.com |
api.example.com |
Same-Site | Cross-Origin |
https://example.com |
http://example.com |
Same-Site | Cross-Origin |
example.com |
google.com |
Cross-Site | Cross-Origin |
3. ์ฟ ํค Domain ์ต์
ํต์ฌ ์์น
์ฟ ํค์ domain ์ต์
์ ๋ช
์ํ์ง ์์ผ๋ฉด, ์ฟ ํค๋ ์ฃผ์ ์์ ํ ๊ฐ์๋์๋ง ์ ํจ!!
์ค์ ๋ณ ๋์
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. ๊ตํ
ํต์ฌ ์ ๋ฆฌ
- Site ≠ Origin
- Same-Site: ๋ฃจํธ ๋๋ฉ์ธ๋ง ๊ฐ์ผ๋ฉด OK
- Same-Origin: Protocol + Domain + Port ๋ชจ๋ ๊ฐ์์ผ ํจ
- ์ฟ ํค๋ domain ์ต์
์ด ํต์ฌ
domain: ".example.com"→ ๋ชจ๋ subdomain ๊ณต์- ์ต์ ์์ → ์ ํํ ํธ์คํธ๋ง
- CORS๋ ๋ณ๊ฐ ์ ์ฑ
- ์ฟ ํค ์ ์ฑ (๋ธ๋ผ์ฐ์ ) ≠ CORS(์๋ฒ)
- ๋ ๋ค ๋ง์ถฐ์ผ ์ฑ๊ณต!
- ์ค๋ฌด์์๋ Same-Site/Cross-Origin์ด ์ผ๋ฐ์
- ํ๋ก ํธ์๋์ API๋ฅผ subdomain์ผ๋ก ๋ถ๋ฆฌ
domain: ".example.com"+sameSite: "lax"+ CORS ์ค์
โกโกโกโก ๋ช ํํ ์ดํดํ์..!
CORS, Site, Origin, ์ฟ ํค ์ ์ฑ ์ ๊ฐ๊ฐ ๋ค๋ฅธ ๋ณด์ ๋ ์ด์ด!
๊ฐ๋ ์ ๋ช ํํ ์ดํดํ๊ณ , ๊ฐ ๋ ์ด์ด๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ์ค์ ํด์ผ Cross-Origin ์ธ์ฆ์ด ์ฑ๊ณต!
์ฐธ๊ณ ์๋ฃ
'TIL' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [251102 TIL] PostgREST vs GraphQL(Hasura)๊ธฐ์ ์คํ ๋น๊ต (0) | 2025.11.02 |
|---|---|
| [251031 TIL] naver ๋ก๊ทธ์ธ ๊ตฌํ with Supabase 3ํธ (1) | 2025.10.31 |
| [251026 TIL] ์ค์ ์ Apollo Client ๊ตฌํ๊ธฐ (0) | 2025.10.26 |
| [251024 TIL] Hasura(GQL) ์ฌ์ฉ์ 3rd-Party ์ฟ ํค ์ ์ฑ ๋ฌธ์ (0) | 2025.10.24 |
| [251024 TIL] Server ์ฉ Apollo Client ์์ฑ์ ์ฃผ์์ ! (0) | 2025.10.24 |