socketio / socket.io

Realtime application framework (Node.JS server)
https://socket.io
MIT License
60.9k stars 10.09k forks source link

CORS works even if it is not enabled #5148

Open alexandrmucha opened 1 month ago

alexandrmucha commented 1 month ago

Describe the bug I am using socket.io in nuxt. Even though I didn't enable cors, it is possible to connect from a different origin than the one nuxt is running on. E.g. it runs on localhost:3000 and I can connect from localhost:8000. Is this a bug or is it my fault?

To Reproduce

Please fill the following code example:

Socket.IO server version: ^4.7.5

Server

import type { NitroApp } from "nitropack";
import { Server as Engine } from "engine.io";
import { Server } from "socket.io";
import { defineEventHandler } from "h3";
import { registerSocketHandlers } from "../sockets";

export default defineNitroPlugin((nitroApp: NitroApp) => {
  const engine = new Engine();
  const io = new Server();

  io.bind(engine);

  registerSocketHandlers(io);

  nitroApp.router.use("/socket.io/", defineEventHandler({
    handler(event) {
      engine.handleRequest(event.node.req, event.node.res);
      event._handled = true;
    },
    websocket: {
      open(peer) {
        const nodeContext = peer.ctx.node;
        const req = nodeContext.req;

        // @ts-expect-error private method
        engine.prepare(req);

        const rawSocket = nodeContext.req.socket;
        const websocket = nodeContext.ws;

        // @ts-expect-error private method
        engine.onWebSocket(req, rawSocket, websocket);
      }
    }
  }));
});

Socket.IO client version: ^4.7.5

Client

socket: io('http://localhost:3000', { transports: ['websocket', 'polling', 'flashsocket'] }),

Expected behavior It will not be possible to connect from an origin other than the one defined or on which nuxt is running.

Platform:

Additional context Add any other context about the problem here.

darrachequesne commented 1 month ago

Hi!

CORS only applies to HTTP long-polling, but you use WebSocket first: ['websocket', 'polling', 'flashsocket']. If you switch back to ['polling', 'websocket'], it should fail as expected.

Note: the "flashsocket" transport does not exist anymore.

alexandrmucha commented 1 month ago

Hi, thank you for your answer.

When I used the default transport method, the connection failed even though I defined the origin correctly. From what I read, it was caused by "the default transportation method is not always allowed by all servers": Stackoverflow

Can you confirm that the error was caused by an unsupported transport method and not an error with the cors configuration?

darrachequesne commented 1 month ago

The Stackoverflow post uses an old version of the socket.io package (2.2), you need to configure the cors option:

const io = new Server({
  cors: {
    origin: ["http://localhost:8000"]
  }
});

Reference: https://socket.io/docs/v4/handling-cors/

alexandrmucha commented 1 month ago

Thats the problem. It doesnt work even though I set this origin correctly.

Access` to XMLHttpRequest at 'http://localhost:3000/socket.io/?EIO=4&transport=polling&t=P3kAbNS' from origin 'http://localhost:8000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. websocket.ts:6

const io = new Server({ cors: { origin: ["http://localhost:8000"] } });

i tried it also without http

darrachequesne commented 1 month ago

Hmm, that's weird... Could you please check if you are able to reach the Socket.IO server with:

$ curl -v -H "origin: http://localhost:3000" "http://localhost:8080/socket.io/?EIO=4&transport=polling"

It should return something like:

*   Trying 127.0.0.1:3000...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET /socket.io/?EIO=4&transport=polling HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/7.81.0
> Accept: */*
> origin: http://localhost:3000
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: http://localhost:3000
< Vary: Origin
< Content-Type: text/plain; charset=UTF-8
< Content-Length: 118
< cache-control: no-store
< Date: Fri, 26 Jul 2024 10:34:04 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< 
* Connection #0 to host localhost left intact
0{"sid":"IMkIs3bah6ZNcXwVAAAA","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":20000,"maxPayload":1000000}

Reference: https://socket.io/docs/v4/troubleshooting-connection-issues/

alexandrmucha commented 1 month ago

My server is running on port 3000 and I'm trying to connect from 8000, so I modified the command to include those addresses.

curl -v -H "origin: http://localhost:8000" "http://localhost:3000/socket.io/?EIO=4&transport=polling"

it returned me this:

* Host localhost:3000 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:3000...
* Connected to localhost (::1) port 3000
> GET /socket.io/?EIO=4&transport=polling HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.7.1
> Accept: */*
> origin: http://localhost:8000
>
* Request completely sent off
< HTTP/1.1 200 OK
< content-type: text/plain; charset=UTF-8
< content-length: 118
< cache-control: no-store
< date: Fri, 26 Jul 2024 13:52:46 GMT
< connection: close
<
0{"sid":"iEWn6snaZTIUWBrqAAAA","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":20000,"maxPayload":1000000}* Closing connection
alexandrmucha commented 1 month ago

and it returns the same thing when I try to set the origin to not allowed in the command

alexandrmucha commented 1 month ago

Also I dont see any cors header in network tab in devtools

alexandrmucha commented 1 month ago

@darrachequesne I tried adding the cors header manually and it works then, could there be a problem with adding the headers? Can you please check my nuxt socket.io plugin code to see if I have a misconfigured server? Or is it possible that there is a bug in socket.io itself?

import type { NitroApp } from "nitropack";
import { Server as Engine } from "engine.io";
import { Server } from "socket.io";
import { defineEventHandler } from "h3";
import { registerSocketHandlers } from "../sockets";

export default defineNitroPlugin((nitroApp: NitroApp) => {
  const engine = new Engine();

  const io = new Server({
    cors: {
      origin: "http://localhost:8000",
    }
  });

  io.bind(engine);

  registerSocketHandlers(io);

  nitroApp.router.use("/socket.io/", defineEventHandler({
    handler(event) {

      // Here I manually added the cors origin header
      event.node.res.setHeader("Access-Control-Allow-Origin", "http://localhost:8000");

      engine.handleRequest(event.node.req, event.node.res);
      event._handled = true;
    },
    websocket: {
      open(peer) {
        const nodeContext = peer.ctx.node;
        const req = nodeContext.req;

        // @ts-expect-error private method
        engine.prepare(req);

        const rawSocket = nodeContext.req.socket;
        const websocket = nodeContext.ws;

        // @ts-expect-error private method
        engine.onWebSocket(req, rawSocket, websocket);
      }
    }
  }));
});
semiharslanait commented 1 month ago

To summarize, while both configurations handle CORS, they do so at different stages and for different purposes. The origin option in the Server constructor controls whether the server will accept connections from a given origin, and the Access-Control-Allow-Origin header in the response ensures that the browser will accept the response from the server.

So they are not same things and socket.io initaly not offering to send header to browser when you give origin