Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgraded to 4.x and objects in socket.request.session no longer exists #3933

Closed
Rc85 opened this issue May 18, 2021 · 6 comments
Closed

Upgraded to 4.x and objects in socket.request.session no longer exists #3933

Rc85 opened this issue May 18, 2021 · 6 comments
Labels
documentation Improvements or additions to documentation

Comments

@Rc85
Copy link

Rc85 commented May 18, 2021

Describe the bug
socket.request.session is always a new session even after declaring req.session.user with a value.

To Reproduce

Setup Socket.io with express-session middleware.

Socket.IO server version: ^4.1.1

Server

import { Server, Socket } from 'socket.io';
import session from 'express-session';

const io = new Server(http.createServer(/* express app */), { /* cors /* });

const wrapper = (middleware: any) => (socket: Socket, next: any) => middleware(socket.request, {}, next);

io.use(wrapper(session(/* session configs */)));

io.on('connection', (socket: Socket) => {
  const req = socket.request

  console.log(req.session) // Typescript will warn that session is not in IncomingMessage
  
  // console.log output shows no user
  /* Session {
    cookie: {
      path: '/',
      _expires: 2021-05-18T13:06:36.146Z,
      originalMaxAge: 3600000,
      httpOnly: true,
      domain: undefined
    }
  } */
});

app.use((req, resp, next) => {
  console.log(req.session) // user is in session
  req.io = io;

  next();
});

app.post('/login', (req, resp, next) => {
  const user =  // login user

  req.session.user = user
});

// server listen and stuff

Socket.IO client version: ^4.1.1

Client

import { io } from "socket.io-client";

const socket = io("http://localhost", {});

socket.on("connect", () => {
  console.log(`connect ${socket.id}`);
});

socket.on("disconnect", () => {
  console.log("disconnect");
});

Expected behavior
socket.request.session should have user property in it when initializing session in express middleware (eg. - req.session.user = user)

Platform:

  • Device: Desktop and Mobile
  • OS: Windows 10 and Android 10

Additional context

@Rc85 Rc85 added the bug Something isn't working label May 18, 2021
@Rc85
Copy link
Author

Rc85 commented May 18, 2021

Downgraded back to 2.4.1 (for the 5th time), changed the imports from named to default and user is in socket.request.session again.

@microspec-chris
Copy link

I'm having the similar issue with the following versions:
express 4.17.1
express-session 1.17.2
socket.io 3.1.2

@amrap030
Copy link

amrap030 commented Apr 1, 2022

I also have this bug using:

express 4.17.1
express-session 1.17.1
socket.io 4.1.3

Please fix this bug, we need the session to be shared between express and socket.io

@darrachequesne
Copy link
Member

OK, so I think I found the culprit.

The middleware at the Socket.IO level only has access to the socket.request object, which is the first request of the session. It is not able to add the cookie in the response headers, hence the creation of a new session each time the socket reconnects.

To be able to add the cookie, we need to intercept the handshake:

const io = new Server(httpServer, {
  allowRequest: (req, callback) => {
    // with HTTP long-polling, we have access to the HTTP response here, but this is not
    // the case with WebSocket, so we provide a dummy response object
    const fakeRes = {
      getHeader() {
        return [];
      },
      setHeader(key, values) {
        req.cookieHolder = values[0];
      },
      writeHead() {}
    }
    sessionMiddleware(req, fakeRes, () => {
      if (req.session) {
        // trigger the setHeader() above
        fakeRes.writeHead();
        // manually save the session (normally triggered by res.end())
        req.session.save();
      }
      callback(null, true);
    });
  }
});

io.engine.on("initial_headers", (headers, req) => {
  if (req.cookieHolder) {
    headers["set-cookie"] = req.cookieHolder;
    delete req.cookieHolder;
  }
});

That sounds a bit far-fetched, but it should do the trick. I will add an example in the documentation.

darrachequesne added a commit to socketio/socket.io-website that referenced this issue Apr 4, 2022
@darrachequesne
Copy link
Member

OK, so I've added an example in the documentation: https://socket.io/how-to/use-with-express-session

Please ping me if something is missing!

@darrachequesne darrachequesne added documentation Improvements or additions to documentation and removed bug Something isn't working labels Apr 4, 2022
@Rc85
Copy link
Author

Rc85 commented Jun 14, 2022

I've decided to try upgrading to 4.x today from 2.x and running into issues with properties in req.session from showing up. The session object is in request now, so that is good news. My set up is a little different as I have 4 session middlewares (eg. admin, merchant, customer, and courier) and depending on what endpoint the user is sending a request from, I have to authenticate that user using the specified session middleware.

With that set up, I also have to use the session middleware depending on which namespace the client is connecting from. For example, if they are connecting from io('/merchant') then I have to do .io.of('/merchant').use on my server.

I've followed the guide you posted, but the issue I am running into is setting up the server options using the example posted above here. I try just using one of my session middleware with your example but I am still not getting user in req.session. Here are some of my code.

// The 4 session middleware setup I have
app.use(/^\/api\/v2\/(account|restaurants)\/.*/, customerSessionMiddleware);
app.use(/^\/api\/v2\/merchant\/.*/, merchantSessionMiddleware);
app.use(/^\/api\/v2\/admin\/.*/, adminSessionMiddleware);
app.use(/^\/api\/v2\/courier\/.*/, courierSessionMiddleware);

const io = new Server(server, {
  pingTimeout: 30000,
  cors: {
    origin: 'http://localhost:3000',
    credentials: true
  },
  allowRequest: (req, callback) => {
    const fakeRes = {
      getHeader() {
        return [];
      },
      setHeader(key: string, values: string[]) {
        req.cookieHolder = values[0];
      },
      writeHead() {}
    };
    // trying with just the merchant session middleware
    merchantSessionMiddleware(req as Request, fakeRes as unknown as Response, () => {
      if (req.session) {
        // trigger the setHeader() above
        fakeRes.writeHead();
        // manually save the session (normally triggered by res.end())
        req.session.save();
      }
      callback(null, true);
    });
  }
});

io.engine.on('initial_headers', (headers: { [key: string]: string }, req: IncomingMessage) => {
  if (req.cookieHolder) {
    headers['set-cookie'] = req.cookieHolder;

    delete req.cookieHolder;
  }
});

merchantSocket(io)

And then in merchantSocket.

export const merchantSocket = (io: Server) => {
  io.of('/merchant').use((socket, next) =>
    merchantSessionMiddleware(socket.request as Request, {} as Response, next as NextFunction)
  );

  io.of('/merchant').on('connection', (socket: Socket) => {
    const req = socket.request;

    socket.on('join_location', (data) => {
      req.session.reload(async (err: any) => {
        if (err) console.log(err);
        req.session.save();

        console.log(req.session);
      });
    });
  });
}

I log into my app, emit the join_location event and this is what I get in console.log, no user.

Session {
  cookie: {
    path: '/',
    _expires: 2022-06-14T22:29:36.375Z,
    originalMaxAge: 3600000,
    httpOnly: true
  }
}

#4383 got it working. So what am I doing wrong? And with a 4 session middleware setup, how do I configure the server options for 4 middlewares?

darrachequesne added a commit to socketio/engine.io that referenced this issue Feb 6, 2023
This commit implements middlewares at the Engine.IO level, because
Socket.IO middlewares are meant for namespace authorization and are not
executed during a classic HTTP request/response cycle.

A workaround was possible by using the allowRequest option and the
"headers" event, but this feels way cleaner and works with upgrade
requests too.

Syntax:

```js
engine.use((req, res, next) => {
  // do something

  next();
});

// with express-session
import session from "express-session";

engine.use(session({
  secret: "keyboard cat",
  resave: false,
  saveUninitialized: true,
  cookie: { secure: true }
});

// with helmet
import helmet from "helmet";

engine.use(helmet());
```

Related:

- #668
- #651
- socketio/socket.io#4609
- socketio/socket.io#3933
- a lot of other issues asking for compatibility with express-session
dzad pushed a commit to dzad/socket.io that referenced this issue May 29, 2023
vanquisher0411 added a commit to vanquisher0411/socketio_project that referenced this issue Feb 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

4 participants