How to return ForbiddenError from auth directive

Hey there,

I’m using a directive to handle auth in my schema:

class AuthDirective extends SchemaDirectiveVisitor {
  visitObject() {}

  visitFieldDefinition(field) {
    const { resolve = defaultFieldResolver } = field;

    field.resolve = (source, resolveArgs, context, info) => {
      if (!context.requestedScopes) {
        context.requestedScopes = new Set();
      }

      for (let scope of this.args.requestedScopes) {
        context.requestedScopes.add(scope);
      }

      const result = checkAuth(context);

      if (result.message) {
        // errorResponse returns ForbiddenError from apollo-server-errors
        throw errorResponse(
          context.req.res,
          result.message,
          result.status,
          result.headers
        );
      }

      return resolve.call(this, source, resolveArgs, context, info);
    };
  }
}

And its doing the right thing in that it’s throwing errors when the auth is wrong.

However I think because of where the error is thrown, it’s being treated as a GraphQLError instead of an ApolloError.
This means the response is being sent as a 500 instead of a 403.
Moreover it means expected auth errors are being logged to sentry when I want them to be filtered out. I’m using the sentry plug in as described here.

How can I have these errors parsed correctly?

1 Like

You’re essentially wrapping the resolver and invoking this auth-checking on each invocation of the resolver. Without this approach, do you experience the same behavior (a 500 instead of a 403) by throwing your errorResponse (which I see you claim returns a ForbiddenError) directly from a resolver? What if you throw the ForbiddenError directly?

Separately, are you actually hoping to do this on each resolver, or would another approach be to check what is going to be accessed before beginning execution?

Yeah good points, particularly the last one. For now we’re mostly applying this directive at the top level so I don’t think it’s having much of an effect, but definitely something for us to consider :thinking:

I tried throwing a ForbiddenError directly from the resolver and the same thing still happens.

What’s being done currently seems to be a recommended route based on this blog

I’m wondering if the fact that throwing the error directly still results in a GraphQLError is pointing to something else being configured incorrectly? Although the docs do suggest a GraphQLError is what I should expect since it’s occurring inside a resolver

OK So the reason this wasn’t working is that in the Sentry plugin snippet above I need to be checking err.originalError like so:

          if (err.originalError instanceof ApolloError) {
            continue;
          }