Search code examples
javaspringgraphqlgraphql-java

GraphQL field level authorization and nullability


We've implemented field level authorization in the type resolvers of our server, where a field is only returned with a value if the user has access to it, otherwise "null" is returned (with information in the extensions). The reason why we are not just returning an error (with an empty data response) is that we have web apps that are used by a variety of users with different access levels, and we don't want (and in some circumstances can't) write queries fitting for every access pattern. Our client has separate logic to deal with authorization, so that the missing information is handeled correctly.

Now we also wanted to add nullability information to our GraphQL schema, but that turned out to be difficult with the above way of handling missing access. If a field technically is never returned null from our data, we would like mark it as non-nullable in the schema as well. But since missing authorization can make the field null while resolving GraphQL, it is not working well in all situations.

I think my only options are:

  • Remove the non-nullable information from parts of the schema that can be affected by authorization checks
  • Build custom queries on the client for the different access types (that would at least allow us to return an error instead of making a field "null", but would mean a lot of work to cover all possible access rules)
  • Somehow remove the entire field from the response before GQL validates it, but that sounds very dirty and also probably breaks the GQL "contract" between server and client.

Has anyone else dealt with field-level authorization, common queries and nullability in GQL and has some other suggestions how to handle this?


Solution

  • Here are some thoughts I hope you find useful.

    Nullability

    Nullability on output types is generally regarded as not the best idea in many cases. I'll quote Production Ready GraphQL here (the book is worth its weight in gold to anyone designing GraphQL schemas, can not recommend it enough):

    So here are a few guidelines that I use for nullability when designing schemas:

    • For arguments, non-null is almost always better to allow for a more predictable and easy to understand API. (If you’re adding an argument, nullable is the best choice to avoid breaking existing clients)
    • Fields that return object types that are backed by database associations, network calls, or anything else that could potentially fail one day should almost always be nullable.
    • Simple scalars on an object that you know will already have been loaded on the parent at execution time are generally safe to make non-null.

    One reason nullability on the output is dangerous is that if a non-null field does end up being null, the whole parent object is nulled too. And if that parent is also non-null... well, you can easily end up bombing your entire object graph for no good reason. This article also has a decent summary of the situation and best practices.

    GraphqlFieldVisibility

    • A very neat way to handle per-field authorization (when possible) instead is to provide a custom GraphqlFieldVisibility for each user (of rather tenant). This way, you have no issues with nullable fields, because the users already can not even see (nor request) the fields they're not authorized to see. The effective schema their requests are executed against is already filtered to only include the permitted fields. This approach does not apply for fields where you need to inspect the runtime value of the inputs or outputs before you make the decision on whether a field is accessible or not, but works wonderfully everywhere else (e.g. for role-based or access-list based authorization).

    Here's the pseudo-code for this:

    //Do this on each request*
    GraphQLCodeRegistry codeRegistry = schema.getCodeRegistry().transform(c -> c.fieldVisibility(new AuthVisibility(currentUser)));
    GraphQL runtime = GraphQL.newGraphQL(schema.transform(s -> s.codeRegistry(codeRegistry)));
    

    Then implement AuthVisibility using the current user (or whatever else is applicable) to decide whether a field is visible to that user. I e.g. made use of Spring Security for this, and invoked the same logic it normally invokes to decide if the underlying resolver method can be called by the current user. I can probably dig out some of that code should you need it.

    *You can also somehow cache the resulting GraphQL object per user session, but it's probably not needed, as the transformations involved are trivial.