0

I would like to use a GraphQLDirective to facilitate authorization while using graphql-kotlin-ktor-server.

This question is about the approach that I am currently planning to use. Is this a good idea? Are there any best-practices that I am missing?

I have a GraphQLDirective called Rbac that I use to annotate a query like this:

@Rbac(resource = "test", action = "create")
suspend fun testQuery(
    dfe: DataFetchingEnvironment
): Boolean

I use a directive wiring to do the authorization like this:

override fun onField(environment: KotlinFieldDirectiveEnvironment): GraphQLFieldDefinition {
    val directive = environment.directive

    // Get info from directive, e.g. which resource and action are required to get access
    val resource = directive.getArgument("resource").argumentValue.value.toString()
    val action = directive.getArgument("action").argumentValue.value.toString()

    // Get user id from context, e.g. who is requesting
    val context: UserContext = dataEnv.graphQlContext.get(UserContext::class)
    val user = context.userId.toString()

    // Check whether user can access resource and action
    when {
        myRbac.check(user, resource, action) -> {
            originalDataFetcher.get(dataEnv)
        }
        else -> {
            throw GraphQLKotlinException("Unauthorized")
        }
    }
}

So, nice and good. Works like a charm. However, should I forget to add the Rbac annotation I am screwed, because then the authorization will not be called because there is no directive.

To improve, my idea was to use a custom DataFetcher in addition.

Add a flag to the context when the authorization was done.

rbac.check(user, resource, action) -> {
    dataEnv.graphQlContext.put("rbac", true)
    originalDataFetcher.get(dataEnv)
}

Reject in the custom DataFetcher when the flag is not present.

class CustomDataFetcher : SimpleKotlinDataFetcherFactoryProvider() {
    override fun functionDataFetcherFactory(target: Any?, kClass: KClass<*>, kFunction: KFunction<*>): DataFetcherFactory<Any?> {
            return DataFetcherFactory {
                object : FunctionDataFetcher(target, kFunction) {
                    override fun get(environment: DataFetchingEnvironment): Any? {
                        val rbac = environment.graphQlContext.get<Boolean>("rbac")
                        if (rbac == true) {
                            return super.get(environment)
                        }

                        throw GraphQLKotlinException("Unauthorized")
                    }
                }
            }
    }
}

In other word, when I forget to add the annotation, the flag will not be there, and the DataFetcher will reject the call.

1
  • I realized that it can be simplified by moving the whole authorization check to the custom data fetcher. The directive can be accessed like this from the data fetcher get method: environment.fieldDefinition.getAppliedDirective("rbac") So now directive wiring needed. Everything is done in the data fetcher. the directive is still used, though.
    – Pete
    Commented Jul 10 at 11:04

0

Browse other questions tagged or ask your own question.