Skip to content
This repository was archived by the owner on Dec 19, 2023. It is now read-only.

Pass reactive context into GraphQL context #437

Open
Toerktumlare opened this issue Jul 5, 2020 · 10 comments
Open

Pass reactive context into GraphQL context #437

Toerktumlare opened this issue Jul 5, 2020 · 10 comments

Comments

@Toerktumlare
Copy link

Toerktumlare commented Jul 5, 2020

Is your feature request related to a problem? Please describe.
After looking at the webflux examples, reading more i finally got graphql working in my Webflux application. Had to downgrade from 7.1.0 to 7.01 because of some other bug that people already have reported.

I use authentication in my webflux app and since the underlying graphql java engine only supports CompletableFuture and not Mono or Flux, the the ReactiveSecurityContextHolder.getContext() will always return null since this is using the reactor context which is not supported in CompletableFuture.

I have been trying to look into how to be able to pass in the reactor context into the graphql context but i have only been working for a day with this, if someone has any input in how to implement this or point me in the right direction i would be super happy to give it a go.

By digging into the code i see that the ServerWebExchange is used in the DefaultGraphQLSpringInvocationInputFactory#create and then during GraphQLSingleInvocationInput#createExecutionInput it looks like it's getting passed into the GraphQL context.

What i have been trying to figure out is how to access the context in the GraphQLQueryResolver in the regular Datafetchers you get access to the DataFetchingEnvironment where you can extract the context, but i can't figure out how it is done i this project.

Describe the solution you'd like
Be able to use the ReactiveSecurityContextHolder.getContext() or any type of reactive context in queries.

Describe alternatives you've considered
If someone could point me in the right direction i could experiment in trying to implement this myself.

@magnusp
Copy link

magnusp commented Aug 24, 2020

While this may not get you all the way it may point you in the right direction. I'm also struggling with getting the subscriber context to materialize in my resolvers and have gotten something working. Unfortunatly I am not in a position to share the complete source but I can give you a few snippets (backed by spring).

First of all you are not limited to returning CompletableFuture or similar in your resolvers - these are the "default generic wrappers". Additional wrappers can be registered as such:

	@Bean
	List<SchemaParserOptions.GenericWrapper> genericWrappers() {
		return List.of(
			SchemaParserOptions.GenericWrapper.withTransformer(
				Mono.class,
				0,
				(mono, dataFetchingEnvironment) -> {

					ServerHttpRequest incomingRequest = dataFetchingEnvironment
						.<GraphQLSpringServerWebExchangeContext>getContext()
						.getServerWebExchange()
						.getRequest();
					return mono.subscriberContext(Context.of(ServerHttpRequest.class, incomingRequest)).toFuture();
				},
				type -> type
			)
		);
	}

In your case I'm guessing you should be able to merge with ReactiveSecurityContextHolder.getContext(). Note that the wrapper must return a CompletableFuture. Also note that there is no wrapper for Flux since AFAIK CompletableFuture is only a single value. Wrapping Flux would be akin to myFlux.collectList().toFuture() and thus making the Flux have no purpose in regard to backpressure. If you still want to experiment with Flux a wrapper for that would be registered as such:

	@Bean
	List<SchemaParserOptions.GenericWrapper> genericWrappers() {
		return List.of(
			SchemaParserOptions.GenericWrapper.withTransformer(
				Mono.class,
				0,
				(mono, dataFetchingEnvironment) -> mono.toFuture(),
				type -> {
					return type;
				}
			),
			SchemaParserOptions.GenericWrapper.listCollectionWithTransformer(
				Flux.class,
				0,
				flux -> flux.collectList().toFuture()
			)
		);
	}

I'm not really sure why a variant of listCollectionWithTransformer isn't provided where DataFetchingEnvironment is passed to the transformer.

@Toerktumlare
Copy link
Author

i actually ended up changing library away from kickstart to https://github.com/graphql-java/graphql-java since this functionality was not described or answered by any maintainer.

@jinwg
Copy link

jinwg commented Jan 10, 2021

I found the ReactiveContext already lost when building GraphqlContext, got a null SecurityContext in custom Context builder. i end up with a WebFilter to put the authentication object into ServerWebExchange attributes, then make it accessible in DataFetchingEvnrionment context. this looks really ugly but i cannot find another better one. there is another way to use SecuirtyContextStorage but that need to use a session, really dislike that. any other advice?

@ooga
Copy link

ooga commented Jan 11, 2021

I have used generic wrapper (thanks to @magnusp) to reapply reactive context manually

First I create custom GraphQL context holding Authentication

public interface ContextWithAuthentication {

    Authentication getAuthentication();
}

Recreate context magically for all resolvers returning Mono

GenericWrapper.withTransformer(
    Mono.<class,
    0,
    (mono>, environment) -> {
        Object context = environment.getContext();
        if (context instanceof ContextWithAuthentication) {
            val contextWithAuthentication = (ContextWithAuthentication) context;
            val authentication = contextWithAuthentication.getAuthentication();
    
            return mono
                .contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication))
                .toFuture();
        }

        return mono.toFuture();
    },
    t -> t
)

In my resolver and all methods of my reactive stream, I'm able to use security method like @PreAuthorize("hasRole('all_user_read')")

I don't know if this solution is better 😄

@jinwg
Copy link

jinwg commented Jan 12, 2021

@ooga, thanks for your advice. @magnusp's solution is definitely better one but i cannot make it work with latest 8.11 version of kickstart spring grphqal. can you confirm which version are you working with? also very interesting how did you fill the authentication into your customized context, do you mind share more idea please.

@ooga
Copy link

ooga commented Jan 12, 2021

I just pushed a tiny example repo to help you

https://github.com/ooga/example-graphql-webflux-security

@jinwg
Copy link

jinwg commented Jan 13, 2021

@ooga, that is far more than a help. appreciate your time.

@jinwg
Copy link

jinwg commented Jan 28, 2021

@ooga, any experience to work with GraphqlFieldVisibility to hide fields base on the user roles in reactive context? SchemaDirectiveWiring can change the field definition, but cannot hide it from Introspection call as the "GraphqlFiledVisbility" dose.

@ooga
Copy link

ooga commented Jan 28, 2021

@jinwg I just created a discussion to handle this new topic
#523

@ermadmi78
Copy link

Hi All.

See Kotlin Spring Boot GraphQL Server with Spring Security authentication and authorization example here:
https://github.com/ermadmi78/kobby-gradle-example

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants