How to return error response in apollo link?

1.6k Views Asked by At

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

2

There are 2 best solutions below

0
On

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);
});
0
On

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