Skip to content

A simple and light-weighted (0 dependency) helper function for authorizing subscription with apollo-server

Notifications You must be signed in to change notification settings

adri1wald/graphql-authorize-subscription

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

graphql-authorize-subscription

A simple and light-weighted (0 dependency) helper function for authorize subscription with apollo-server

Why would you want to use this library?

As for today the apollo server doesn't provides any feature to authorize graphQL subscription in resolver level. You can authenticate the users over connection level (with onConnection options) but production-grade projects usually have numerous authorizing contexts over subscription resolvers, and it's messy and hard to handle all of this authorizations within onConnection handler only.

There's no "Best practices" to do this right now, So I've ended up creating my own capsulized component to handle these resolver-level authorization for subscriptions.

Basically the library adds once executed resolver at the very front of asyncIterator, so you can use this function as pre processor of your subscription too.

Installation

npm install graphql-authorize-subscription

or

yarn add graphql-authorize-subscription

Usage Examples

This library simply exports one higher-order function called withAuthorization. It's usage is almost same with withFilter.

it receives originResolver, your original subscription ResolverFn, as first argument.

and authorizationResolver as second argument. authorizationResolver is a simple resolver that returns anything and can be async function.

if you throw errors in authorizationResolver, the socket connection closes immediately and client will receive graphQL error payloads.

Common Usage

import { withFilter } from 'graphql-subscriptions';
import { withAuthorization } from 'graphql-authorize-subscription';
// import withAuthorization from 'graphql-authorize-subscription'; You can use the default export too.

export const authorizedResolvers = {
  Subscription: {
    authorizedSubscription: {
      subscribe: withAuthorization(
        // First argument is your original subscription resolver function.
        (parent, { somePrivateId }) => pubsub.asyncIterator(`${CONSTANTS.SUBSCRIPTION.SUBSCRIPTIONKEY}.${somePrivateId}`),
        
        // Here in second argument, define your own authorization resolver. it receives all the arguments that normal resolver get.
        async (parent, { somePrivateId }, context, info) => {
          if (!await doSomeHeavyAuthorizationTaskWithDatas( ... )) {
            throw errors.UNAUTHORIZED();
          }

          return true;
        }),
    },
  },
};

Usage over withFilter

import { withFilter } from 'graphql-subscriptions';
import { withAuthorization } from 'graphql-authorize-subscription';

export const authorizedResolvers = {
  Subscription: {
    authorizedSubscription: {
      subscribe: withAuthorization(
        // First argument is your original subscription resolver function.
        withFilter(
          (parent, { somePrivateId }) => pubsub.asyncIterator(`${CONSTANTS.SUBSCRIPTION.SUBSCRIPTIONKEY}.${somePrivateId}`),
          (payload, { somePrivateId }) => somePrivateId === payload.authorizedSubscription.somePrivateId,
        ),
        
        // Here in second argument, define your own authorization resolver. it receives all the arguments that normal resolver get.
        async (parent, { somePrivateId }, context, info) => {
          if (!await doSomeHeavyAuthorizationTaskWithDatas( ... )) {
            throw errors.UNAUTHORIZED();
          }

          return true;
        }),
    },
  },
};

Usage as pre processor

import { withFilter } from 'graphql-subscriptions';
import withPreprocess from 'graphql-authorize-subscription';

export const authorizedResolvers = {
  Subscription: {
    preprocessedSubscription: {
      subscribe: withPreprocess(
        withFilter(
          (parent, { taskId }) => pubsub.asyncIterator(`${CONSTANTS.SUBSCRIPTION.TASKS}.${taskId}`),
          (payload, { taskId }) => taskId === payload.preprocessedSubscription.taskId,
        ),
        async (parent, { taskId }, context, info) => {
          if (isTaskDoneAlready(taskId)) {
            throw new Error('ERR_TASK_FINISHED'); // In your front-end code mark the task as finished.
          }
          
          if (isTaskNotStarted(taskId)) {
            await startTask(taskId) // start the task first.
            
            // Maybe you want to just let the task start asynchronously, or even in next tick.
            // If the task resolves as done within synchronous level, the task done event will not sent to the subscriber
            // as it resolves before the pubsub subscription made.
            
            process.nextTick(() => startTask(taskId));
          }

          return true;
        }),
    },
  },
};

About

A simple and light-weighted (0 dependency) helper function for authorizing subscription with apollo-server

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 100.0%