6

I'm using a passport local strategy that works well with express:

passport.use(localStrategy);
passport.serializeUser((user, done) => done(null, JSON.stringify(user)));
passport.deserializeUser((obj, done) => done(null, JSON.parse(obj)));
app.use(passport.initialize());
app.use(passport.session());

This localStrategy is doing a Mongoose call to get the user based on his pubKey and I guess that request.user is populated by this way.

I setup my graphql endpoint like this:

app.use('/graphql', bodyParser.json(), graphqlExpress(request => ({
  debug: true,
  schema,
  context: {
    user: request.user,
    req: request,
  },
  formatError: (e) => {
    console.log(JSON.stringify(e, null, 2));
    return e;
  },
})));

And my subscriptions this way:

const ws = createServer(app);

// Run the server
ws.listen(settings.APP_PORT, () => {
  console.log(`App listening on port ${settings.APP_PORT}!`);
  // Set up the WebSocket for handling GraphQL subscriptions
  new SubscriptionServer({
    execute,
    subscribe,
    schema,
    onConnect: (connectionParams, webSocket) => {
      console.log(webSocket.upgradeReq);
      return { user: connectionParams };
    },
  }, {
    server: ws,
    path: '/subscriptions',
  });
});

My session is working well on graphql queries and mutations. But not with my subscriptions.

My goal is to have access to my user session in my subscription resolver context. I may need to access something like request.user in onConnect to populate the context, but I don't know how to do.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Kivutar
  • 73
  • 6

3 Answers3

4

So after some tinkering I've figured out that what you need to do is basically run the everything that recreates the passport for the socket's upgradeReq by re-running the middleware:

// Start with storing the middleware in constants
const passportInit = passport.initialize();
const passportSession = passport.session();
app.use(passportInit);
app.use(passportSession);

...
// Now populate the request with the upgradeReq using those constants
onConnect: (connectionParams, webSocket) => new Promise((resolve) => {
  passportInit(webSocket.upgradeReq, {}, () => {
    passportSession(webSocket.upgradeReq, {}, () => {
      resolve(webSocket.upgradeReq);
    });
  });
}),

If you are using an express-session you need to add that to the above.

Max Gordon
  • 5,367
  • 2
  • 44
  • 70
4

For graphql-ws it works in similar fashion to @Max Gordon answer, just different values must be passed to the calls. Attaching code snippet for expressSession + passport usage. If you don't need express session simply remove this call from onConnect callback.

const sessionInstance = expressSession({
  // options
});
app.use(sessionInstance);

const passportInitialize = passport.initialize();
const passportSession = passport.session();

app.use(passportInitialize);
app.use(passportSession);


const serverCleanup = useServer(
  {
    schema,
    context: async (ctx) => {
       return {
         user: ctx.extra.request.user
       };
    },
    onConnect: async (ctx) =>
      new Promise((resolve) => {
        // Is stored sessionMiddleware() from above
        sessionInstance(ctx.extra.request, {}, () => {
          // Is stored passport.initialize() from above
          passportInitialize(ctx.extra.request, {}, () => {
            // Is stored passport.session() from above
            passportSession(ctx.extra.request, {}, () => {
              resolve(true); // always allow connecting (or check if user is in request already and deny if not, depends what you want to achieve)
            });
          });
        });
      }),
    },
    wsServer,
  );
Morishiri
  • 874
  • 10
  • 23
0

Take a look at graphql-passport. It essentially does the same thing that Max mentions in his answer, but in a simplified package.

Note that this currently works for subscriptions-transport-ws, which is now considered deprecated. I have, as of yet, not found a solution while using the newer replacement, graphql-ws, as that works in a different way to the legacy socket system, and doesn't seem to have access to any kind of request object.

Ryall
  • 12,010
  • 11
  • 53
  • 77
  • 1
    It has access to the request object, please see my answer: https://stackoverflow.com/a/71412629/5069352 – Morishiri Mar 09 '22 at 16:22