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

Use graphql-middleware in subscriptions like queries and mutations #366

Open
jpbidal opened this issue Apr 1, 2021 · 4 comments
Open

Use graphql-middleware in subscriptions like queries and mutations #366

jpbidal opened this issue Apr 1, 2021 · 4 comments
Labels
kind/bug A reported bug.

Comments

@jpbidal
Copy link

jpbidal commented Apr 1, 2021

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!

@maticzav maticzav added the kind/bug A reported bug. label May 23, 2021
@maticzav
Copy link
Owner

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?

@jcpage
Copy link

jcpage commented Dec 7, 2021

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

@jpbidal
Copy link
Author

jpbidal commented Mar 10, 2022

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

@cuzzlor
Copy link

cuzzlor commented Aug 11, 2023

Here's a suggested solution: #561

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug A reported bug.
Projects
None yet
Development

No branches or pull requests

4 participants