DEV Community

Cover image for Solving Cross-Origin Cookies
Freddy Carrillo
Freddy Carrillo

Posted on

Solving Cross-Origin Cookies

Your chat app works locally but your REST API and WebSocket authentication fail in production. Here's why.

I deployed with Vercel (frontend) and with Render (backend). Deploying on different domains makes for many adjustments during development when dealing with cookies. Without the correct configuration, my app failed to send cookies with HTTP requests. This created a problem, I had dual authentication: the HTTP Only cookies acted as my main security, which was set to protect all my REST API endpoints. My WebSocket authorization was protected by the same method, checking the HTTP only cookie, but also relied on its own token to establish a connection. Both expected the same token but delivered through different mechanisms.

My HTTP requests were not receiving the cookie, making my REST API authentication fail. After some research, I changed the cookie's settings:

// From:
sameSite: 'strict'
// To:
sameSite: 'none'
Enter fullscreen mode Exit fullscreen mode

Allowing the cookies to be sent and received with all third-party requests which includes cross-origin cookies. To secure the cookies, I set the secure option to be true to only allow cookies from HTTPS requests. Testing it in deployment fixed the REST API authentication, but failed when the browser settings blocked third-party cookies. This also made WebSocket not connect, since there was no cookie to pass and allow.

Given that constraint, my first attempt was making my frontend proxy the API endpoints. This implementation made domain the same-origin in the browsers perspective, by accessing my API via the client URL. Now the browser saw my API as the same origin, eliminating any cross-origin issues.

// Before proxy
API_URL="https://message-app.render.com/:path"
// After proxy
API_URL="https://message-app.vercel.com/:path"
Enter fullscreen mode Exit fullscreen mode

Each HTTP request is from the same origin, Vercel acts as the middle man, changing the API URL. To add, I needed to set a parameter (withCredentials) in the socket's client configuration to tell the browser to include cookies with the WebSocket handshake.

// Pass token via HTTP requests
const socket = io(URL, { withCredentials: true });
Enter fullscreen mode Exit fullscreen mode

After deployment, the browser now allowed for users to be authenticated, but it was still not allowing my cookies to be passed to the WebSocket connection, and that made sense after I understood why.

Even in a properly configured cross-origin environment, some browsers still do not send cookies with WebSocket handshakes. A WebSocket handshake is when the HTTP connection upgrades, allowing users to communicate in real time. WebSocket connections upgrade from the HTTP to the WebSocket protocol. While my REST API requests went through Vercel's proxy (same-origin), the WebSocket upgrade connected directly to Render (cross-origin). This made the browser not allow WebSocket access to cookies, so my authentication failed.

The solution was to store the token in memory and attach it to Socket.io's parameters, which would be extracted in authorization.

// Fetch token from authenticated endpoint, pass explicitly
const socket = io(URL, {
  auth: { token: authToken }
});
Enter fullscreen mode Exit fullscreen mode

With AuthContext already established with React's Context, on each page load the context will send an HTTP requests to the backend with the HTTP Only cookie attached. Rather than exposing the 5-day HTTP Only cookie token, I create a separate 1-hour token specifically for WebSocket authentication. This token is fetched from an authenticated endpoint (which validates the cookie), stored in memory, and passed via Socket.io's auth parameter. If an attacker somehow steals this token via XSS, their access is limited to 1 hour and cleared on page refresh.

This dual authentication strategy maintains security while ensuring cross-origin compatibility. If you're deploying a real-time application with frontend and backend on different domains, you'll likely need a similar approach. Browser cookies won't send credentials with WebSocket handshakes, regardless of your CORS configuration.

Top comments (0)