GraphQL field level authorization and nullability

GraphQL field level authorization and nullability


2

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?

2 Answers
2


1

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.

3

  • 1

    Thank you for your answer. That was very insightful. Especially the part about GraphqlFieldVisibility. Doesn't this error out if a user requests a field that is not part of the visible schema for them? I will mark this as an answer.

    – puelo

    Jul 26 at 18:21

  • @puelo It does, yes, as if they requested any other non existing field. Depending on who/what your client is, that may be a good or a bad thing. E.g. in a multi tenant application where the clients are maintained by the tenants themselves, this works wonders. If your client needs to dynamically adjust to different permissions (which is your situation, if I got it right), you can theoretically leverage introspection to make the decision, but that's cumbersome (effectively like HATEOAS), so the visibility approach isn't that great.

    – kaqqao

    Jul 26 at 22:04


  • Thanks! We actually somewhat have a mix of both and the GraphqlFieldVisibility might still be useful for self-documenting the schema via introspection for clients with different access levels (so that are not presented with any fields that they cannot actually query). For now we will remove the not-null constraint for the information which can be null via authorization and reevaluate once/if the proposal has made it into the spec.

    – puelo

    Jul 26 at 22:29


0

Adding this as an answer for the future. It seems like there as a somewhat progressed proposal in the GraphQL Spec working group, which would solve that issue, by allowing client controlled nullability (basically changing the nullability of a field in the request):

https://github.com/graphql/graphql-wg/blob/main/rfcs/ClientControlledNullability.md

Here is an excerpt of the proposal:

Each client controlled nullability designator overrides the
schema-defined nullability of the field it’s attached to for the
duration of the operation.

!

The proposed client-controlled required designator would have similar,
but not identical semantics to the current schema-defined Non-Null.
Specifically if a required field resolves to null, then null
propagation extends to the nearest optional parent rather than the
nearest nullable parent. In the event that no optional parent exists,
the data field of the response will be null.

?

The proposed client-controlled optional designator would have
identical semantics to the current schema-defined default behavior.
Fields that resolve to null return null for that field. Additionally,
fields marked with ? act as a stopping point for null propagation
caused by required fields.

Update 18.09.2023: Sadly the RFC was updated to only include "!", but omit the counter-part in "?". This was done to reduce the complexity and move the RFC forward. This means that the RFC in it’s current form will not be a full solution to the issue outlined in my question. I will still keep this answer here in case it becomes useful again.

1

  • Didn't know about this proposal. Thanks for pointing it out. Sounds useful, seeing how often nullability causes undue trouble.

    – kaqqao

    Jul 26 at 22:27



Leave a Reply

Your email address will not be published. Required fields are marked *