Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessing cookies in subscription resolver functions #424

Closed
joealden opened this issue Aug 7, 2018 · 17 comments
Closed

Accessing cookies in subscription resolver functions #424

joealden opened this issue Aug 7, 2018 · 17 comments

Comments

@joealden
Copy link

joealden commented Aug 7, 2018

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!

@joealden
Copy link
Author

joealden commented Aug 8, 2018

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.

@captDaylight
Copy link

@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?

@joealden
Copy link
Author

joealden commented Aug 8, 2018

@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!

@captDaylight
Copy link

@joealden really appreciate it! this is super helpful, I was very close but this ironed out the kinks.

@frederikhors
Copy link

@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?

@joealden
Copy link
Author

@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.

@stale
Copy link

stale bot commented Nov 23, 2018

Due to inactivity of this issue we have marked it stale. It will be closed if no further activity occurs.

@stale stale bot added the stale label Nov 23, 2018
@joealden
Copy link
Author

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?

@stale
Copy link

stale bot commented Jan 22, 2019

Due to inactivity of this issue we have marked it stale. It will be closed if no further activity occurs.

@sscotth
Copy link

sscotth commented Feb 25, 2019

Do not close

@stale
Copy link

stale bot commented May 24, 2019

Due to inactivity of this issue we have marked it stale. It will be closed if no further activity occurs.

@stale stale bot added the status/stale label May 24, 2019
@frederikhors
Copy link

No.

@stale stale bot removed the status/stale label May 24, 2019
@bakhaa
Copy link

bakhaa commented May 26, 2019

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.

@mikestecker
Copy link

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!

@stale
Copy link

stale bot commented Sep 15, 2019

Due to inactivity of this issue we have marked it stale. It will be closed if no further activity occurs.

@stale stale bot added the status/stale label Sep 15, 2019
@frederikhors
Copy link

No stale.

@stale stale bot removed the status/stale label Sep 15, 2019
@saihaj saihaj mentioned this issue Dec 5, 2021
32 tasks
@ardatan
Copy link
Collaborator

ardatan commented Mar 22, 2022

In new GraphQL Yoga v2, you can access platform independent Request object that contains all the headers passed from the server. And I assume the users here are using Node so anyone can access the cookies passed from the server from req like below;

(root, args, context, info) => {
   context.req.cookies // Express
   context.request.headers.get('Cookie') // Non node env
   context.req.headers.cookie // Any other Node env
}

@ardatan ardatan closed this as completed Mar 22, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants