I'm using Apollo Server 2 and Express.js vanilla (with apollo-server-express).
Everything works good also with Subscriptions except the Express session mechanism.
The problem:
I'm using cookie-session (https://github.com/expressjs/cookie-session, but I think this is the same for express-session middleware) and when my browser start a new connection with my server the ApolloServer onConnect hook doesn't have the req attribute and neither req.session and so on...
What I can do is to parse the cookies from webSocket.upgradeReq.headers.cookie in onConnect lifecycle hook, but it seems to me very hacky.
The code:
const { ApolloServer } = require('apollo-server-express')
const typeDefs = require('../src/graphql/types')
const resolvers = require('../src/graphql/resolvers')
const models = require('../src/models')
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, connection }) => {
// connection exists only on webSocket connection
if (connection) {
return {
currentUser: connection.context.currentUser // <-- I NEED THIS!
}
}
// if not a (webSocket) connection it is a "default" HTTP call
return {
models,
currentUser: { id: req.user.id }
}
},
subscriptions: {
onConnect: (connectionParams, webSocket) => {
// "connectionParams" is from the client but I cannot use it because cookies are HTTP-Only
// I can retrieve cookies from here: "webSocket.upgradeReq.headers.cookie" but then I need to parse them which seems a bit hacky to me
// return { currentUser: req.user.id } // <-- I NEED THIS (req.user.id doesn't exists)!
}
}
})
module.exports = apolloServer
I can't find anything on Apollo Server Docs site (for other topics very well documented! Just this: https://www.apollographql.com/docs/apollo-server/features/subscriptions.html#Context-with-Subscriptions).
Where am I doing wrong?
StackOverflow question: https://stackoverflow.com/questions/52280481/graphql-subscription-websocket-nodejs-express-session-with-apollo-server-2
Any updates on this?
Any news?
The way i found to deal with this using express session is doing something like this:
subscriptions: {
onConnect: async (connectionParams, webSocket) => {
const wsSession = await new Promise(resolve => {
// use same session parser as normal gql queries
session(webSocket.upgradeReq, {}, () => {
if (webSocket.upgradeReq.session) {
resolve(webSocket.upgradeReq.session);
}
return false;
});
});
// We have a good session. attach to context
if (wsSession.userId) {
return { session: wsSession };
}
// throwing error rejects the connection
throw new Error('Missing auth token!');
},
}
@adavia Where is your session variable coming from?
In
session(webSocket.upgradeReq, {}, () => {
if (webSocket.upgradeReq.session) {
resolve(webSocket.upgradeReq.session);
}
return false;
});
EDIT: nvm, figured it out, comes from
import session from "express-session";
const mySession = session({
store: new SomeStore(),
resave: false,
secret: "mysecret",
saveUninitialized: false,
}),
basically your session is the intialized session();
session support is pretty bad, if you use passport it makes you add a bunch of get requests, but graphql is built around POST, unclear how to mix the two
The way i found to deal with this using express session is doing something like this:
subscriptions: { onConnect: async (connectionParams, webSocket) => { const wsSession = await new Promise(resolve => { // use same session parser as normal gql queries session(webSocket.upgradeReq, {}, () => { if (webSocket.upgradeReq.session) { resolve(webSocket.upgradeReq.session); } return false; }); }); // We have a good session. attach to context if (wsSession.userId) { return { session: wsSession }; } // throwing error rejects the connection throw new Error('Missing auth token!'); }, }
I get the following warnings as a result of including the code snippet provided by @adavia. I'm not sure how to add the missing options as I don't really understand what's going on in the code. Could I please get some guidance?
express-session deprecated undefined resave option; provide resave option server\server.js:95:66 er.js:95:66
express-session deprecated undefined saveUninitialized option; provide saveUninitialized option server\server.js:95:66zed option server\server.js:95:66
express-session deprecated req.secret; provide secret option server\server.js:95:66
Probably because of webSocket.upgradeReq.session.
express-session middleware is attached on the request.
Here is my implementation.
import session from 'express-session';
.......
const mySession = session(sessionConf);
app.use(mySession);
......
subscriptions: {
onConnect: async (connectionParams, webSocket, { request }) => {
const subscriptionContext: SubscriptionContext = await new Promise(res => {
mySession(request, {}, () => {
const userSession: UserSession = (request?.session as UserSession).user;
res(
userSession
? ({
req: request,
user: userSession,
} as SubscriptionContext)
: null
);
});
});
if (!subscriptionContext) {
throw new AuthenticationError('Unauthorized');
}
return subscriptionContext;
},
},
context: ({ req, res, connection }) => {
const subscriptionContext: SubscriptionContext = connection?.context;
const ctx: MyContext = subscriptionContext
? {
req: subscriptionContext.req,
user: subscriptionContext.user,
res,
}
: {
req,
user: req.session?.user,
res,
};
return ctx;
},
Most helpful comment
The way i found to deal with this using express session is doing something like this: