2

I am trying to get an app that uses socket.io v.3.1.1 to work on production.

It works well on development using webpack devServer for the client on 3000 and nodemon for the server on 4000.

But when I put it on the production server the client complains with:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:5003/socket.io/?EIO=4&transport=polling&t=NUmy2Us.

Server

import express from 'express'
import { createServer } from 'http'
import { Server } from 'socket.io'

const app = express()
const prod = process.env.NODE_ENV === 'production'
const port = process.env.PORT || prod ? 5003 : 4000
const httpServer = createServer(app)

const io = new Server(httpServer, {
  cors: {
    origin: '*',
    methods: ['GET', 'POST']
  }
})

const connections = []

io.on('connection', (socket) => {
  connections.push(socket)
  console.log(`Socket id ${socket.id} connected`)

  socket.on('disconnect', () => {
    connections.splice(connections.indexOf(socket), 1)
  })
})

httpServer.listen(port, () => console.log(`App listening on port ${port}.`))
....

Client

...
import { io } from 'socket.io-client'

const port = process.env.NODE_ENV === 'development' ? '4000' : '5003'
const socket = io(`http://localhost:${port}`)

This set up does work on development but when I put it on production on port 5003, it throws the CORS.

On the nginx server blocks I got

location /thisapp/ {
  auth_basic $authentication;
  auth_basic_user_file /var/www/.htpasswd;
  try_files $uri $uri/ =404;
}

# And other proxies for express routing
location /api/process {
  proxy_pass http://localhost:5003/api/process;
}

location /api/process/download {
  proxy_pass http://localhost:5003/api/process/download;
}

I know the app is listening on 5003 on the server.

Pm2 log App

App listening on port 5003.

When I look at the network on the web sockets tab

On Dev I get this:

enter image description here

And on Production this:

enter image description here

The production server runs on https with let's encrypt but this has never been an issue for other apps I have run, I wonder if socket.io needs me to do something about it thou.

I tried multiple combinations of different approaches but I always get this:

enter image description here

Álvaro
  • 2,255
  • 1
  • 22
  • 48

2 Answers2

4

I just ran into this last week - though not with Socket.io - so hopefully I can help.


Before the answer, a link to point you towards some reading on what's going on: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#how_to_allow_cross-origin_access


If your NGINX has access to use more_set_headers then try adding this inside your location block:

  more_set_headers 'Access-Control-Allow-Origin: *';

If that works, you can next try paring that back further to:

  more_set_headers 'Access-Control-Allow-Origin: http://localhost:5003';

If you don't have access to more_set_headers you can use add_headers instead, but it's confusingly named. It doesn't only add headers; it will also remove any headers applied by blocks further up the hierarchy, like in your server block. more_set_headers will not remove those headers and is truly additive.

The syntax differs a bit. If you're forced to use add_headers try:

  add_header Access-Control-Allow-Origin *;

Or more restrictive:

  add_header Access-Control-Allow-Origin http://localhost:5003;

Finally, if you need to support multiple origins you can do it like this to have NGINX automatically return a header that is compatible with the origin making the request.

Outside your server block:

  map $http_origin $allow_origin {
      ~^(http://localhost:5003|http://example.com)$ $http_origin;
  }

Inside your location block:

add_header Access-Control-Allow-Origin $allow_origin;

I'm not 100% sure about the syntax for using map together with more_set_headers but this should get you 95% of the way there.

Slbox
  • 10,957
  • 15
  • 54
  • 106
2

In the end after a lot of back and forth it turned out not to have anything to do with the headers.

I think my problem was twofold.

On Dev I am using webpack devServer for the front end on port 3000 and nodemon for the backend on 4000, so I was not using Nginx or Pm2, and it worked just fine.

Therefore on production I did not have any block for socket.io and Pm2 was running in cluster mode with two instances, the moment I changed it to a single instance on Pm2 and added the Nginx location block for socket.io, it started to work with this:

Server

import express from 'express'
import { createServer } from 'http'
import { Server } from 'socket.io'

const app = express()
const prod = process.env.NODE_ENV === 'production'
const port = process.env.PORT || prod ? 5003 : 4000
const httpServer = createServer(app)

const io = new Server(httpServer)
// Or to make it also work on Dev 
const io = new Server(httpSever, { cors: true })

const connections = []

io.on('connection', (socket) => {
  connections.push(socket)
  console.log(`Socket id ${socket.id} connected`)

  socket.on('disconnect', () => {
    connections.splice(connections.indexOf(socket), 1)
  })
})

httpServer.listen(port, () => console.log(`App listening on port ${port}.`))

Nginx

location /socket.io/ {
  proxy_pass http://localhost:5003/socket.io/;
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
  proxy_set_header Host $host;
}

Client

import { io } from 'socket.io-client'
const socket = io()

// Or to make it also work on Dev 
const dev = process.env.NODE_ENV === 'development'
const socket = dev ? io('http:localhost:4000') ? io()
Álvaro
  • 2,255
  • 1
  • 22
  • 48