Hi, I'd like to quickly thank the contributors of this project for making graphql server creation so easy!
Here is my issue:
I am writing an API where an authentication cookie is sent along with the request. I am using the express cookie-parser middleware to parse the cookie and place it in request.cookies. This works fine for query and mutation resolver functions as I have access to the request object through the context argument of the resolver function. However, the express request object is not passed to subscription resolver functions (for a good reason I presume), meaning that I cannot access the cookie this way.
To my understanding, WebSocket requests are sent over HTTP, and from a little reading online, I think this means that WebSocket requests are able to access HTTP cookies.
If what I have said is correct, how can I access the cookies that are sent along with the request?
I have seen issue #393 with an accompanying PR #394. If this gets merged, could I access the cookies through the webSocket object passed into the context?
If so, could we get this PR merged (if there is nothing blocking it)? If cookies are able to be used with WebSockets, I think that it would be a great feature to add.
Here is the github link to the project that I am working on for reference: https://github.com/joealden/talq-api/.
It might also be worth mentioning that I have seen this https://www.apollographql.com/docs/react/advanced/subscriptions.html#authentication where you can send connectionParams with the request. But the issue is that because I am using a cookie that I want to be httpOnly, I cannot access it's contents and send it as a connectionParam.
Please correct me if any of what I have said is wrong, and thanks for your time!
Update: I have found out that I can indeed access the cookies sent with the request if #394 is merged via context.webSocket.upgradeReq.headers.cookie.
@joealden I know your question is about websockets specifically, but I've been looking into adding cookie-parser middleware form my mutations and queries. Can you show how you're setting up that middleware?
@captDaylight yeah sure, as I mentioned above, this is the repo that I am using it in: https://github.com/joealden/talq-api/ (it's written in TypeScript).
https://github.com/joealden/talq-api/blob/92ef03bef75518e8e1cdb763f8890824af10e3a7/src/index.ts#L36: this is how I added the cookie-parser middleware. This means that I can now access the cookies sent with the request from the context arg in any query or mutation resolver function (if you named this arg context, through context.request.cookies).
https://github.com/joealden/talq-api/blob/92ef03bef75518e8e1cdb763f8890824af10e3a7/src/resolvers/utils.ts#L11: here I have a getUserId helper function that receives the context as an argument and checks if the jwt that is in the cookie is valid. Also in that same file I have a checkAuth function that I use when I need to know if the user is logged in but don't need to use their user ID stored in the token cookie.
https://github.com/joealden/talq-api/blob/92ef03bef75518e8e1cdb763f8890824af10e3a7/src/resolvers/Query.ts#L5: then in the resolver functions, I use one of those above helper functions if I need to check if the user is logged in. If the user is logged in, the helper function will return and the resolver function will continue to execute, if the user is not logged in, the helper function will throw an error, causing the resolver function to halt execution and error out.
This code was inspired by @wesbos's (unreleased) Advanced React codebase over at https://github.com/wesbos/Advanced-React. I altered the code to fit my use case. The helper function style code is inspired by this example code created by the @prismagraphql team: https://github.com/prismagraphql/prisma/blob/master/examples/authentication/src/utils.js#L6. I just altered how I retrieved the token because I wanted to use cookies instead of an Authorization HTTP header.
Thanks to Wes and the Prisma team for the great learning resources!
@joealden really appreciate it! this is super helpful, I was very close but this ironed out the kinks.
@joealden the problem is still there I think, right?
I'm using express.js vanilla with Apollo Server 2 and I'm stucked here like you.
I'm using cookies to store my session using https://github.com/expressjs/cookie-session.
I can get cookies with webSocket.upgradeReq.headers.cookie but I don't know how to validate session (and currentUser) in ApolloServer's onConnect hook.
Can you hint me?
@frederikhors Sorry, I don't think I can really help you with that as your question is about sessions and Apollo server specifically, which I don't have that much experience with.
In my case, if you are interested, I forked graphql-yoga (https://github.com/joealden/graphql-yoga) as the maintainers haven't responded to this issue and I needed the feature. I then merged #394 and it worked like a charm.
If its any help, here is a github repo where I am handling subscription auth through cookies using my forked version of yoga (https://github.com/joealden/talq-api). You can see in the subscription resolvers and the utility functions I made for handling auth how I check for cookies correctly depending on if it is a subscription action. Unfortunately I am using graphql-yoga and not Apollo server, so you might have to change the code a bit.
Hope that is of some help. Also, it would be great if a maintainer took some time to look over this issue.
Due to inactivity of this issue we have marked it stale. It will be closed if no further activity occurs.
This issue should be kept open, I don't understand why stale bot closes issues 'if no further activity occurs'. This is is still a feature that I think would be a worthy addition to graphql-yoga, but no maintainer has responded to this issue yet. Why should it be closed if it is still an issue?
Due to inactivity of this issue we have marked it stale. It will be closed if no further activity occurs.
Do not close
Due to inactivity of this issue we have marked it stale. It will be closed if no further activity occurs.
No.
I solved this problem in the onConnect callback function.
const options = {
cors: { credentials: true, origin },
port: PORT,
subscriptions: {
onConnect: async (connectionParams, webSocket) => {
try {
const promise = new Promise((resolve, reject) => {
session(webSocket.upgradeReq, {}, () => {
resolve(webSocket.upgradeReq.session.passport);
});
});
const user = await promise;
return user;
} catch (error) {
console.log('error', error);
}
},
},
};
Next, when you initialize the server, you can get the user in context.
const server = new GraphQLServer({
typeDefs,
resolvers,
context: ({ request, connection }) => {
let user = request ? request.user : null;
if (connection) {
if (connection.context.user) user = connection.context.user;
}
return { user, request, pubsub };
},
});
An example can be found here https://github.com/bakhaa/pw/blob/master/api/app.js.
Any news on this? I've tried looking at examples by @bakhaa but I can't seem to get it to work. Here's my code so far but I can't get the userId in context:
here are my server options:
cors: {
credentials: true,
origin: process.env.FRONTEND_URL
},
subscriptions: {
onConnect: async (connectionParams, webSocket) => {
const header = webSocket.upgradeReq.headers.cookie;
const { token } = cookie.parse(header);
try {
const promise = new Promise((resolve, reject) => {
const { userId } = jwt.verify(token, process.env.APP_SECRET);
resolve(userId);
});
const user = await promise;
return user;
} catch (err) {
throw new Error(err);
}
}
}
here's my server init:
function createServer() {
return new GraphQLServer({
typeDefs: "src/schema.graphql",
resolvers: {
Mutation,
Query,
Subscription,
Conversation
},
resolverValidationOptions: {
requireResolversForResolveType: false
},
validationRules: [depthLimit(10)],
uploads: {
maxFileSize: 10000000, // 10 MB
maxFiles: 20
},
context: req => ({
...req,
db
})
});
}
the problem I'm having is in the context part. I can't figure out the proper syntax. I've tried variations of context: ({ request, connection }) (which breaks everything) and then context: (req, connection) (connection always ends up undefined so I end up with Message: Cannot read property 'userId' of undefined, Location: [object Object], Path: message,node,conversation,name ).
If anyone has any suggestions to get this to work, that would be greatly appreciated!
Due to inactivity of this issue we have marked it stale. It will be closed if no further activity occurs.
No stale.
Most helpful comment
@captDaylight yeah sure, as I mentioned above, this is the repo that I am using it in: https://github.com/joealden/talq-api/ (it's written in TypeScript).
https://github.com/joealden/talq-api/blob/92ef03bef75518e8e1cdb763f8890824af10e3a7/src/index.ts#L36: this is how I added the cookie-parser middleware. This means that I can now access the cookies sent with the request from the context arg in any query or mutation resolver function (if you named this arg context, through context.request.cookies).
https://github.com/joealden/talq-api/blob/92ef03bef75518e8e1cdb763f8890824af10e3a7/src/resolvers/utils.ts#L11: here I have a
getUserIdhelper function that receives the context as an argument and checks if the jwt that is in the cookie is valid. Also in that same file I have acheckAuthfunction that I use when I need to know if the user is logged in but don't need to use their user ID stored in the token cookie.https://github.com/joealden/talq-api/blob/92ef03bef75518e8e1cdb763f8890824af10e3a7/src/resolvers/Query.ts#L5: then in the resolver functions, I use one of those above helper functions if I need to check if the user is logged in. If the user is logged in, the helper function will return and the resolver function will continue to execute, if the user is not logged in, the helper function will throw an error, causing the resolver function to halt execution and error out.
This code was inspired by @wesbos's (unreleased) Advanced React codebase over at https://github.com/wesbos/Advanced-React. I altered the code to fit my use case. The helper function style code is inspired by this example code created by the @prismagraphql team: https://github.com/prismagraphql/prisma/blob/master/examples/authentication/src/utils.js#L6. I just altered how I retrieved the token because I wanted to use cookies instead of an Authorization HTTP header.
Thanks to Wes and the Prisma team for the great learning resources!