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.