graphql-java/graphql-java-spring

When used with webflux and spring security the current implementation will cause the SecurityContext to be lost

huntj88 opened this issue · 8 comments

Spring webflux uses Reactor for its async logic. Its made of a chain of Mono<T>'s. That chain can carry a context that can be used anywhere in your code within the scope of that single request. The SecurityContext resides within that chain context.

That chain is broken by the underlying implementation of DataFetcher which uses CompletableFuture for its own async code. As a result the User is no longer authenticated at any point after the logic is delegated to GraphQL-Java.

One solution to this is to add the SecurityContext to the ExecutionInput's context, so that we are at least able to access it (even if its not the normal way)

Some example code written in kotlin

@Component
@Internal
class DefaultGraphQLInvocation(private val graphQL: GraphQL) : GraphQLInvocation {
    
    override fun invoke(invocationData: GraphQLInvocationData, webRequest: ServerWebExchange): Mono<ExecutionResult> {
        return ReactiveSecurityContextHolder.getContext().flatMap { securityContext ->
            val executionInput = ExecutionInput.newExecutionInput()
                .query(invocationData.query)
                .operationName(invocationData.operationName)
                .variables(invocationData.variables)
                .context(securityContext)
                .build()

            Mono.fromCompletionStage(graphQL.executeAsync(executionInput))
        }
    }
}

I feel like providing the context in this not normal way could be really confusing for developers though

I haven't played with this graphql-java-spring yet (was just looking over the issues to see if it was mature enough to even give it a shot) - but one thing that might be possible to pass that security context to the data fetchers is to wrap the executor with a DelegatingSecurityContextExecutorService

In our existing application, we pass in an ExecutorService everywhere we do completable future stuff with graphql resolver / data fetchers and we wrap the ExecutorService that we create with the spring security DelegatingSecurityContextExecutorService - otherwise the spring security context gets lost the first time it goes off into the other executor service.

(so when we make the calls to things like CompletableFuture.supplyAsync we pass in the executor to use - both so we can control how the executor is created / how many threads it uses - and also so we can have the spring security context available where necessary)

The underlying graphql-java engine is as you say based on CompletableFuture.

So indeed you will lose the "viral Reactor context " object without special code. if graphql-java was a Reactor only based library then we could have opted into the "magic context passing" but it is not.

As you have said, any context that you need in downstream Monos will need to be put in the graphql context and patched into the DataFetcher call to the Mono.

I feel like providing the context in this not normal way could be really confusing for developers though

If you are a Reactor only coder then yes I agree. However the graphql-java-spring-webflux is based on a non flux (by completely asynchronous) CompletableFuture. Sorry but there is not a lot we could do with that other than rewrite if from scratch to be Mono's all the way down and then we would have two engines almost a like except for the outer calling signatures.

Note we would also break others if they used RxJava instead Reactor say since they would be a RxJava+Reactor project.

Is it possible that a wrapper around DataFetcher could be added? Ideally, such a wrapper would expose a Mono that automatically reapplied the security context.

I can't seem to find GraphQLInvocation in the current version of graphql spring boot webflux.
How best to deal with this with the current version?
thank you

So there is no solution for this, any workarounds?

@huntj88 Some custom working solution. You can see here:
https://github.com/IvanGTrendafilov/Auth0GraphQLReactiveSecurity/blob/main/src/main/java/com/auth0/example/SecurityContextGraphQLInvocation.java how I am building security context just before GraphQL invocation with the specific token which I have decoded and authorities are granted on authentication. After that you can access the context from DataFetcher environment and you can build custom method level security based on the context. Simple Kotlin example:
fun hasPermission(securityContext: SecurityContext, permissions: Permissions): Boolean { if (Objects.isNull(securityContext) || Objects.isNull(securityContext.authentication)) { return false; } val authorities = securityContext.authentication.authorities log.info { "$authorities for User: ${securityContext.authentication.principal}" } return authorities.contains(SimpleGrantedAuthority(permissions.permission)); }
You can provide stronger security and can validate(decode) Bearer token on Reactive Filter Level with Resource server and build custom security context with permissions token just before GraphQL invocation :)
I hope this will help for future implementations

This project is now archived in favor of the official Spring GraphQL integration.