maticzav / graphql-middleware

Split up your GraphQL resolvers in middleware functions

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Use graphql-middleware in subscriptions like queries and mutations

jpbidal opened this issue · 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!

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?

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

maticzav/graphql-shield#27 (comment)

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

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"
}

Here's a suggested solution: #561