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.
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.
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,
);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With