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

socket.request.user empty after v2 to v4 migration #5020

Open
SubJunk opened this issue May 6, 2024 · 4 comments
Open

socket.request.user empty after v2 to v4 migration #5020

SubJunk opened this issue May 6, 2024 · 4 comments

Comments

@SubJunk
Copy link

SubJunk commented May 6, 2024

Describe the bug
Hi and thanks for this useful module. We are upgrading from v2 to v4 and have been struggling to get the behavior we had before, would love some input.

We have a Node.js/Angular app with many dynamic subdomains, and we have a separate Node.js websockets app. In v2 we did not specify the cookie domain or CORS origin and it all worked, with each subdomain getting its own subdomain-scoped cookie. This scope is important because users have different logins for different subdomains.

In v4 we haven't been able to achieve that same behavior. The closest we have come is if we specify the cookie domain and CORS origin as the domain (not the subdomain), socket.request.user is there. That's not a workable solution though, because that stops people being able to have different sessions for different subdomains.

If we do not specify the cookie domain and CORS origin, the websocket connection succeeds but it does not contain socket.request.user.

It works properly on localhost during development, it is only when subdomains are introduced that it breaks.

To Reproduce

Socket.IO server version: 4.5.1+

Websockets Server

const cookieSettings: CookieOptions = { };

const sessionMiddleware = session({
  cookie: cookieSettings,
  resave: true,
  saveUninitialized: true,
  store: connectMongoStore,
  name: 'shared_session',
  secret: 'foo'
});

app.use(sessionMiddleware);
app.use(passport.initialize());
app.use(passport.session());

passport.serializeUser(function(user: Partial<IUserSchema>, done) {
  done(null, user._id);
});

passport.deserializeUser(function(id: string, done) {
  User.findOne({ _id: id }, function(err, user) {
    ...
    done(null, user);
  });
});

// withCredentials strategy from https://socket.io/docs/v4/client-options/#withcredentials
const corsOptions: cors.CorsOptions = {
  credentials: true,
  // the following function is from https://socket.io/docs/v4/server-options/#cors
  origin: (_req, callback) => {
    callback(null, true);
  },
};

const io = new Server(server, {
  cookie: true,
  cors: corsOptions,
});

// Register middleware for all namespaces, approach from https://socket.io/how-to/register-a-global-middleware
// Attempts to combine the above approach with https://socket.io/how-to/use-with-passport
// In v2 this section was using the passport.socketio module which has since been deprecated
const wrap = middleware => (socket: { request: any; }, next: any) => middleware(socket.request, {}, next);
const defaultNamespace = io.of('/');
const buildNamespace = io.of('/build');
for (const ns of [defaultNamespace, buildNamespace]) {
  ns.use(wrap(sessionMiddleware));
  ns.use(wrap(passport.initialize()));
  ns.use(wrap(passport.session()));
}

Node.js server (where login happens)

const cookieSettings = { };
if (!isDevelopment) {
  cookieSettings.secure = true;
  cookieSettings.httpOnly = true;
}
const sharedSessionSettings = {
  cookie: cookieSettings,
  resave: true,
  saveUninitialized: true,
  name: 'shared_session'
};

const mongoSession = session({
  secret: '',
  store: connectMongoStore,
  ...sharedSessionSettings
});

app.use(function useSession(req, res, next) {
  return mongoSession(req, res, next);
});

app.use(passport.initialize());
app.use(passport.session());

Socket.IO client version: 4.5.1+

Client

import { Socket } from 'ngx-socket-io';
@Injectable()
export class WebsocketService extends Socket {
  constructor() {
    super({ url: 'theurl', options: { withCredentials: true } });
  }
}

Expected behavior
I expect socket.request.user to be populated as it was in v2

Platform:

  • Device: Tried on MacBook Pro, Heroku, Azure
  • OS: macOS, Linux, Windows

Additional information
To get from v2 to v4 there were some other related dependency changes. The list is:
ngx-socket-io from 3.4.0 to 4.3.1
passport.socketio replaced with the syntax from jfromaniello/passport.socketio#148 (comment)
socket.io from 2.5.0 to 4.5.1 (also tried 4.7.5)
socket.io-client from 2.5.0 to 4.5.1 (also tried 4.7.5)
socket.io-redis from 5.4.0 to @socket.io/redis-adapter@7.2.0
redis from 3.1.2 to 4.6.13

Hopefully I shared all the relevant parts of code, lmk if I missed anything.

@SubJunk SubJunk added the to triage Waiting to be triaged by a member of the team label May 6, 2024
@darrachequesne darrachequesne added needs investigation and removed to triage Waiting to be triaged by a member of the team labels Sep 17, 2024
@darrachequesne
Copy link
Member

Hi! Were you able to find the culprit?

@SubJunk
Copy link
Author

SubJunk commented Sep 17, 2024

@darrachequesne hi, thanks for the reply, no we didn't. We ended up preventing users from being logged in on different subdomains which is inconvenient for them. Would be very interested in any ideas you have.

@darrachequesne
Copy link
Member

I see you are using Socket.IO middleware:

for (const ns of [defaultNamespace, buildNamespace]) {
  ns.use(wrap(sessionMiddleware));
  ns.use(wrap(passport.initialize()));
  ns.use(wrap(passport.session()));
}

Did you try with io.engine.use()?

function onlyForHandshake(middleware) {
  return (req, res, next) => {
    const isHandshake = req._query.sid === undefined;
    if (isHandshake) {
      middleware(req, res, next);
    } else {
      next();
    }
  };
}

io.engine.use(onlyForHandshake(sessionMiddleware));
io.engine.use(onlyForHandshake(passport.session()));
io.engine.use(
  onlyForHandshake((req, res, next) => {
    if (req.user) {
      next();
    } else {
      res.writeHead(401);
      res.end();
    }
  }),
);

Reference: https://socket.io/how-to/use-with-passport

@SubJunk
Copy link
Author

SubJunk commented Oct 2, 2024

@darrachequesne thanks for replying, I did try that along the way but there were a lot of moving parts so I will try it again and let you know if it changes anything. From memory that io.engine syntax didn't work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants