2

I'm using apollo link in schema stitching as an access control layer. I'm not quite sure how to make the link return error response if a user does not have permissions to access a particular operation. I know about such packages as graphql-shield and graphql-middleware but I'm curious whether it's possible to achieve basic access control using apollo link.

Here's what my link looks like:

  const link = setContext((request, previousContext) => merge({
    headers: {
      ...headers,
      context: `${JSON.stringify(previousContext.graphqlContext ? _.omit(previousContext.graphqlContext, ['logger', 'models']) : {})}`,
    },
  })).concat(middlewareLink).concat(new HttpLink({ uri, fetch }));

The middlewareLink has checkPermissions that returns true of false depending on user's role

const middlewareLink = new ApolloLink((operation, forward) => {
  const { operationName } = operation;
  if (operationName !== 'IntrospectionQuery') {
    const { variables } = operation;
    const context = operation.getContext().graphqlContext;

    const hasAccess = checkPermissions({ operationName, context, variables });
    if (!hasAccess) {
      // ...
    }
  }
  return forward(operation);
});

What should I do if hasAccess is false. I guess I don't need to forward the operation as at this point it's clear that a user does not have access to it

UPDATE

I guess what I need to do is to extend the ApolloLink class, but so far I didn't manage to return error

Damian Green
  • 6,895
  • 2
  • 31
  • 43
Le garcon
  • 7,197
  • 9
  • 31
  • 46

2 Answers2

1

Don't know if anyone else needs this, but I was trying to get a NetworkError specifically in the onError callback using Typescript and React. Finally got this working:

const testLink = new ApolloLink((operation, forward) => {
    let fetchResult: FetchResult = {
        errors: [] // put GraphQL errors here
    }

    let linkResult = Observable.of(fetchResult).map(_ => {
        throw new Error('This is a network error in ApolloClient'); // throw Network errors here
    });
    return linkResult;
});

Return GraphQL errors in the observable FetchResult response, while throwing an error in the observable callback will produce a NetworkError

0

After some digging I've actually figured it out. But I'm not quite sure if my approach is correct.

Basically, I've called forward with a subsequent map where I return an object containing errors and data fields. Again, I guess there's a better way of doing this (maybe by extending the ApolloLink class)

const middlewareLink = new ApolloLink((operation, forward) => {
  const { operationName } = operation;
  if (operationName !== 'IntrospectionQuery') {
    const { variables } = operation;
    const context = operation.getContext().graphqlContext;

    try {
      checkPermissions({ operationName, context, variables });
    } catch (err) {
      return forward(operation).map(() => {
        const error = new ForbiddenError('Access denied');
        return { errors: [error], data: null };
      });
    }
  }
  return forward(operation);
});
Le garcon
  • 7,197
  • 9
  • 31
  • 46