5
votes

I'm using Apollo Server 2 (but this problem is not only with Apollo) 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!).

Where am I doing wrong?

4
how you solved it?Nux

4 Answers

1
votes

I know this old but ran into this issue myself and how i solved it was I ran webSocket.upgradeReq.session through my session middleware.

enter image description here

Then in onConnect:

enter image description here

And you have access to your session

1
votes

This is what ! use

  //Session middleware before resolvers
  const sessionParser = session({
    store,
    secret: config.get('keys.session_secret'),
    //...The rest config here
    }
  });
  const apolloServer = new ApolloServer({
    schema,
    context: ({req, res, connection}: TheContext): TheContext => {
      return {req, res, redisStore: store, connection}; //connection is must here
    },
    subscriptions: {
      onConnect: (_params, _ws, ctx) => {
        return new Promise((resolve, reject) => {
          const req = ctx.request as express.Request;
          const res = ({} as any) as express.Response;

          //Parsing session
          sessionParser(req, res, (_: any) => {
            const session = req.session as any;

            //Getting userId which was stored during login
            const userId = session.userId;

            if (!userId) {
              reject(new AppError('You are not logged in'));
            }

            //This will be available in subscription via connection as
            //something.connection.context.userId
            resolve({userId});
          });
        });
      }
    }
  });

And I use with TypeGraphql like this

  @Subscription(() => Post, {
    topics: ({context}) => `${Topic.DELETED_POST}:${parseUserId(context)}`,
    description: 'To be used in requests list and not for individual request'
  })
  deletedPost(@Root() post: Post) {
    return post;
  }

This is how I get access to userId which was stored in session

export function parseUserId(obj: any): string {
  return obj.connection.context.userId ?? '';
}

Credit to all answers here

0
votes

See this comment: https://github.com/expressjs/cookie-session/issues/117#issuecomment-452046225 by dougwilson to get the session values.

If you just want to get the session contents (and not write back a new session -- which would not be possible without a res anyhow), you could likely just use the cookies module directly, which is what this module uses under the hood to read the cookie and validate the signature. A possible example:

const Cookies = require('cookies')

// …

const cookies = new Cookies(req, null, { keys }) const session =
JSON.parse(Buffer.from(cookies.get('session'),'base64').toString('utf8'))

And onConnect return whatever you need to use on subscribe()

0
votes

https://www.apollographql.com/docs/apollo-server/features/subscriptions.html#Authentication-Over-WebSocket

This seems to suggest you can pass in your required cookie data in the client side to receive in the context on the server.

Pass in params for websocket connection: https://www.apollographql.com/docs/react/advanced/subscriptions.html#authentication