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

Use graphql-middleware in subscriptions like queries and mutations

Open jpbidal opened this issue 3 years ago • 4 comments

Hello, I want to use graphql-middleware in subscriptions to validate if users are authenticated. In queries and mutations it works, but not in subscriptions. Here is my definition of middleware:

import { ApolloError } from 'apollo-server-express'

const isLoggedIn = async (
  resolve: any,
  parent: any,
  args: any,
  context: any,
  info: any,
) => {
  if (context.isUnauthenticated()) {
    throw new ApolloError(`User not authenticated.`)
  }
  return resolve()
}

export const middlewares = {
  Query: {
    myQuery: isLoggedIn
  },
  Mutation: {
    myMutation: isLoggedIn
  },
  Subscription: {
    mySubscription: isLoggedIn,
  },
}

Thanks!

jpbidal avatar Apr 01 '21 16:04 jpbidal

That's interesting!

I know there's a part of the code that should take care of the subscriptions wrapping. Could you compose a short reproduction so I can investigate this?

maticzav avatar May 23 '21 06:05 maticzav

Hey - I commented in a related issue in the graphql-shield project:

https://github.com/maticzav/graphql-shield/issues/27#issuecomment-986198813

It has a lot of detail but the summary is the middleware code that is supposed to wrap the 'subscribe' field ends up finding the subscription's 'resolve' field first and wrapping it instead. Swapping the order of the checks would let it wrap subscription instead (which I believe is preferrable) or some extra work could let it wrap both (which I'm not sure is that useful?)

jcpage avatar Dec 07 '21 07:12 jcpage

Hi! I apologize for the delay of answering your request. I'm using an workaround into each subscription resolve, like @jcpage says.

The isLoggedIn method could be simulated as a console log, but the real action is validate the user data from context.

When I run queries and mutations all works ok, but isLoggedIn method is ignored when I run subscriptions.

I attach an example of subscription server. I hope it helps you. Thank you so much for you job and congrats for it.

import express from 'express';
import http from 'http';
import { ApolloServer } from 'apollo-server-express';
import {
  ApolloServerPluginLandingPageGraphQLPlayground,
  ApolloServerPluginLandingPageDisabled,
} from 'apollo-server-core';
import { execute, subscribe } from 'graphql';
import { SubscriptionServer } from 'subscriptions-transport-ws';
const { graphqlUploadExpress } = require('graphql-upload');
import { schema } from './graphql/schema/schema';
import { createContext } from './context';
import { settings } from './settings';
import { RedisPubSub } from 'graphql-redis-subscriptions';
import Redis from 'ioredis';

async function startApolloServer() {
  const app = express();

  const optionsRedis = {
    host: settings.REDIS_HOST,
    port: settings.REDIS_PORT,
  };

  const pubsub = new RedisPubSub({
    publisher: new Redis(optionsRedis),
    subscriber: new Redis(optionsRedis),
  });

  app.use(graphqlUploadExpress());

  const httpServer = http.createServer(app);

  const subscriptionServer = SubscriptionServer.create(
    {
      schema,
      execute,
      subscribe,
      onConnect: (connectionParams: any) => {
        if (connectionParams.Authorization) {
          return createContext(
            settings,
            pubsub,
            connectionParams.Authorization,
          );
        }
      },
    },
    {
      server: httpServer,
      path: settings.GRAPHQL_ENDPOINT,
    },
  );

  const playground = ApolloServerPluginLandingPageGraphQLPlayground()
  const subscriptions = {
    async serverWillStart() {
      return {
        async drainServer() {
          subscriptionServer.close();
        },
      };
    },
  };

  const apolloServer = new ApolloServer({
    schema,
    context: (expressCtx) =>
      createContext(
        settings,
        pubsub,
        expressCtx.req.headers.authorization,
      ),
    introspection: true,
    plugins: [playground, subscriptions],
  });
  await apolloServer.start();

  apolloServer.applyMiddleware({
    app,
    cors: {
      origin: settings.CORS_ORIGIN,
      methods: settings.CORS_METHODS,
    },
    path: settings.GRAPHQL_ENDPOINT,
  });
  
  httpServer.listen(settings.PORT, () =>
    console.log(
      `🚀  Server ready at ${settings.PROTOCOL}://${settings.HOSTNAME}:${settings.PORT}${settings.GRAPHQL_ENDPOINT} - mode: ${ENVIRONMENTS.NODE_ENV}`,
    ),
  );
  
}

startApolloServer().catch((err) => {
  console.log(err);
});

Some libraries installed:

{
  "apollo-server-core": "^3.6.3",
  "apollo-server-express": "^3.6.3",
  "express": "^4.17.2",
  "graphql": "15.8.0",
  "express-session": "^1.17.1",
  "graphql-middleware": "^6.1.13",
  "graphql-redis-subscriptions": "^2.4.2",
  "graphql-subscriptions": "^2.0.0",
  "graphql-upload": "^13.0.0",
  "graphql-passport": "^0.6.3",
  "subscriptions-transport-ws": "^0.11.0"
}

jpbidal avatar Mar 10 '22 05:03 jpbidal

Here's a suggested solution: https://github.com/dimatill/graphql-middleware/issues/561

cuzzlor avatar Aug 11 '23 08:08 cuzzlor