Cross-Origin 쿠키 해결

발행: (2026년 2월 17일 오전 03:15 GMT+9)
3 분 소요
원문: Dev.to

Source: Dev.to

Cover image for Solving Cross-Origin Cookies

The Issue

  • Frontend deployed on Vercel (different domain)
  • Backend (REST API & WebSocket) deployed on Render

Because the domains differ, cookies are not sent with HTTP requests. The app relied on an HttpOnly cookie for both REST API authentication and WebSocket authorization. Without the cookie, both mechanisms break.

Changing the cookie’s sameSite attribute allowed it to be sent with cross‑origin requests.

// From:
sameSite: 'strict'
// To:
sameSite: 'none'

To keep the cookie secure, the secure flag was also enabled (HTTPS only). This resolved the REST API authentication, but browsers that block third‑party cookies still prevented the cookie from being sent, breaking the WebSocket connection as well.

Making the Frontend a Proxy (Same‑Origin)

The first workaround was to proxy API calls through the Vercel frontend, making the request appear same‑origin to the browser.

// Before proxy
API_URL="https://message-app.render.com/:path"

// After proxy
API_URL="https://message-app.vercel.com/:path"

With this change, the browser no longer treated the REST requests as cross‑origin, and authentication succeeded.

For the WebSocket client, the withCredentials option was added to include cookies during the handshake:

const socket = io(URL, { withCredentials: true });

Why Cookies Still Don’t Reach WebSockets

Even with a properly configured cross‑origin environment, many browsers do not send cookies during the WebSocket handshake. In this setup:

  • REST API requests go through Vercel’s proxy (same‑origin).
  • The WebSocket upgrade connects directly to Render (cross‑origin).

Because the handshake is cross‑origin, the browser blocks the HttpOnly cookie, causing authentication to fail.

Storing the Token in Memory and Using Socket.io Auth

The solution is to avoid relying on cookies for the WebSocket connection. Instead:

  1. Fetch a short‑lived token from an authenticated endpoint (the endpoint validates the HttpOnly cookie).
  2. Store the token in memory (React context, Redux, etc.).
  3. Pass the token via Socket.io’s auth parameter during the handshake.
// Fetch token from authenticated endpoint, then pass explicitly
const socket = io(URL, {
  auth: { token: authToken }
});

Dual‑Authentication Strategy

  • REST API: Continues to use the HttpOnly cookie (e.g., 5‑day lifespan).
  • WebSocket: Uses a separate, short‑lived token (e.g., 1‑hour) stored only in memory.

If an attacker obtains the WebSocket token via XSS, the exposure is limited to the token’s short lifespan and it disappears on page refresh.

Takeaways

  • When frontend and backend are on different domains, cross‑origin cookies require SameSite: 'none' and Secure: true.
  • Browsers may still block cookies on WebSocket handshakes, regardless of CORS settings.
  • Proxying API calls through the frontend can solve same‑origin issues for REST, but not for WebSockets.
  • A token‑based approach for WebSocket authentication (stored in memory) provides a reliable, secure workaround.

If you’re deploying a real‑time app across multiple domains, expect to implement a similar dual‑authentication pattern.

0 조회
Back to Blog

관련 글

더 보기 »

Inertia.js가 조용히 앱을 깨뜨립니다

TL;DR 프로덕션 Laravel 12 + React 19 + Inertia v2 앱에서 몇 주 동안 작업하면서, 진단 비용이 많이 드는 실패 모드에 반복적으로 부딪혔습니다: overlapping visit can...