graphql-subscriptions icon indicating copy to clipboard operation
graphql-subscriptions copied to clipboard

Authorizing subscriptions

Open altschuler opened this issue 8 years ago • 12 comments
trafficstars

I've got a foobarUpdated subscription, but I need to authorize the users who subscribe to it as well as validate the variable (if the given id is not a valid foobar id, subscription doesn't make sense).

Is there a way to deny/cancel a subscription? I'm using withFilter but that only works for filtering after the subscription has been initiated.

altschuler avatar Jul 20 '17 08:07 altschuler

@altschuler I think it should be the responsibility of lower middlewares as pointed out by this article of @helfer. Btw, if you're using Hapi, hapi-nes provides subscription filters that you may be interested in.

anhldbk avatar Jul 21 '17 02:07 anhldbk

@altschuler , @helfer : Would you please tell me what's the difference between withFilter and resolve functions. Are they the same?

anhldbk avatar Jul 21 '17 02:07 anhldbk

@anhldbk The problem is not where to do authorization, but rather that if a user subscribes to something that user is not authorized to, that subscription should not be allowed, and somehow cancelled or denied. As it is, the subscription will be made, but will simply never trigger if the user does not have permission to view the results. It's a solution that works, but it feels somewhat like a hack.

As for your question, resolve enables you to manipulate what the subscription publishes, whereas subscribe in combination with withFilter enables you to filter out results that you don't want to publish under specific circumstances (eg user/state). Kind of like the difference between map and filter :)

altschuler avatar Jul 22 '17 22:07 altschuler

@altschuler Thanks for sharing with me.

anhldbk avatar Jul 23 '17 23:07 anhldbk

@altschuler What you said about authorization is related to the protocol behind, right? The specification has nothing about Authorization.

anhldbk avatar Jul 24 '17 02:07 anhldbk

@altschuler Is this what you want?

const subscriptionServer = SubscriptionServer.create({
  schema: executableSchema,
  execute,
  subscribe,
  onConnect(connectionParams, webSocket) {
    const userPromise = new Promise((res, rej) => {
      if (connectionParams.jwt) {
        jsonwebtoken.verify(connectionParams.jwt, JWT_SECRET,
        (err, decoded) => {
          if (err) {
            rej('Invalid Token');
          }

          res(User.findOne({ where: { id: decoded.id, version: decoded.version } }));
        });
      } else {
        rej('No Token');
      }
    });

    return userPromise.then((user) => {
      if (user) {
        return { user: Promise.resolve(user) };
      }

      return Promise.reject('No User');
    });
  },
// to be continued

(from https://github.com/srtucker22/chatty/blob/master/server/index.js#L61)

anhldbk avatar Jul 25 '17 13:07 anhldbk

@anhldbk That only solve where all subscriptions required authentication

but how about just some of them are required.. then based on the code, there would be more lines to check whether or not the subscription path (e.g. foobarUpdated need to be authenticated)

In Query and Mutation, authentication could be performed in directive or resolver. It would be nice if Subscription also has a way to do authentication in directive or via a property in resolver object (i.e. the same level as subscribe and resolve)

Altiano avatar Jan 31 '18 06:01 Altiano

https://github.com/apollographql/graphql-subscriptions/blob/master/.designs/authorization.md does this help at all? It's a very old doc, unfortunately I don't really know much about this topic.

grantwwu avatar Oct 02 '18 17:10 grantwwu

Not sure, if anyone is still interested, but I found a little hackish, but working solution, for refusing connections in resolvers: first you make authorization in onConnect as pointed by @anhldbk (but don't reject the socket yet, just return the status)

onConnect(connectionParams) {
 // ... do authorization
return { authorized: false }; // or true
}

next off, when declaring your topic in subscribtion you can access the .authorized field:

  @Subscription({
    topics: ({ args, context, payload }) => {
      if (!context.authorized )
      {
          // this gives user error response and cancels subsribtion
          throw new AuthenticationError(`Unauthorized user cannot receive info from this socket`);
      }
      return SOME_TOPIC;
}
  })
  accountBalanceChangeTopic(
//...
}

throwing error inside topics ( or filters) results in socket connection being closed.

nudabagana avatar Mar 18 '19 15:03 nudabagana

Any new info on securing specific subscriptions? In my case I want to only allow the user to subscribe to their own data (based on an owner field).

RWOverdijk avatar Jul 04 '19 08:07 RWOverdijk

i have taken the following approach:

{
  onMessage: {
    subscribe: async (_, { channel }) => {
      // do you auth logic here
      if (channel !== 'happy') {
        unauthorized() // throw an ApolloError
      }
      return pubsub.asyncIterator(MESSAGE_TOPIC)
    }
  }
}

Is this valid?

adri1wald avatar Nov 16 '20 02:11 adri1wald

Not sure if they're best practices, and it's not documented, but see the following two things:

Empirically Apollo Server supports what's needed, it's just not documented well.

spencerwilson avatar Feb 25 '21 01:02 spencerwilson