socketio / socketio/socket.io

socket.request.user empty after v2 to v4 migration

Open
#5,020 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

needs investigation
Dominant language
TypeScript
Stars
63.2k
Forks
10.3k
Avg merge
11d 20h
Merged PRs (30d)
2

Description

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 https://github.com/jfromaniello/passport.socketio/issues/148#issue-865236471
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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the session and Passport middleware entry points shown for the websocket server and the login server, comparing the v2 and v4 migration dependencies. Reproduce the subdomain case with the provided Socket.IO client, cookie, and CORS settings. Done means socket.request.user is populated while separate subdomains retain separate sessions.

Written by the indexing model from the issue text.

Assessment

Tech stack
angular, javascript, mongodb, node.js, redis, typescript
Domain
authentication, backend, networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.