1

I am having a spring webflux based GraphQL application. The application is configured as resourceServer and I am able to access the /graphql endpoint by passing JWT token.

Within the JWT token there are claims that I want to access. I am trying to use the ReactiveSecurityContextHolder.getContext() but getting null every time.

ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Authentication::getPrincipal)
.cast(Jwt.class)

The above code is in the GraphqlQueryResolver class. For graphql I are using the below dependency.

<dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-spring-boot-starter</artifactId>
    <version>12.0.0</version>
</dependency>

Below is the security configuration.

@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {

        return http
                .csrf(spec -> spec.disable())
                .authorizeExchange().pathMatchers("/api/**").permitAll()
                .anyExchange().authenticated().and()
                .oauth2Login()
                .and()
                .oauth2ResourceServer()
                .jwt().and().and().build();

    }
}

Now I am getting a UUID as part of the access token claim which I want to access on my graphql resolver class. But every time I try to get the JWT using the above code I am getting null.

If I try to get the same in @RestController with @AutheticationPrincipal it works perfectly. But I want to access the same using the SecurityContext in graphql resolver class.

Here is a simple resolver

public class CustomerResolver implements GraphQLQueryResolver {

    public Mono<String> getCustomerById (){

        return ReactiveSecurityContextHolder.getContext()
                .map(ctx -> ctx.getAuthentication())
                .map(authentication -> {
                    if(authentication instanceof OAuth2AuthenticationToken){
                        return ((OAuth2AuthenticationToken) authentication).getPrincipal().getAttributes();
                    } else if(authentication instanceof JwtAuthenticationToken){
                        return ((JwtAuthenticationToken) authentication).getTokenAttributes();
                    }
                    return null;
                }).map(jwt -> String.valueOf(jwt));
    }
}

And the corresponding schema file for graphql

type Query {
    getCustomerById: String
}
  • Have you read the spring security documentation on jwts, you should start there. – Toerktumlare Apr 13 '22 at 14:24
  • I am able to use @AuthenticatedPrincipal to get claims in controller but the ReactiveContextHolder.getContext() is always returning null that's where I am facing issue. – Siddharth Chaurasia Apr 13 '22 at 14:37
  • Please check https://stackoverflow.com/questions/71537058/reactive-spring-security-context/71541008#71541008. Also please note that security context will not be available for `/api/**` because of `permitAll()` – Alex Apr 13 '22 at 15:43
  • Hi @Alex, Tried the above but the context is still null possibly due to GraphQL context. – Siddharth Chaurasia Apr 13 '22 at 16:58
  • 1
    could you provide more details regarding "GraphQL context". Also, what is `Okta.configureResourceServer401ResponseBody(http)`? In general if context is empty, most probably your resource server doesn't work as expected. I would suggest to use `org.springframework.security:spring-security-test` and validate resource server https://docs.spring.io/spring-security/site/docs/5.2.12.RELEASE/reference/html/test-webflux.html – Alex Apr 13 '22 at 17:23
  • please put some effort into your question and provide a small working example. The code you have provided is not enough to reproduce the problem which means this question is impossible to answer. – Toerktumlare Apr 13 '22 at 23:08

1 Answers1

0

Try to create minimal example to isolate the issue. The following worked for me with your security configuration but without Okta.configureResourceServer401ResponseBody(http);

@Configuration
@RestController
public class RouteConfig {

    @GetMapping(value = "/api/test")
    public Mono<String> test() {
        return ReactiveSecurityContextHolder.getContext()
                .map(ctx -> ctx.getAuthentication().getPrincipal())
                .cast(Jwt.class)
                .map(jwt -> jwt.getClaimAsString("tenant"));
    }
}

Here is a test using org.springframework.security:spring-security-test

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class RouteConfigTest {
    @Autowired
    private WebTestClient client;

    @Test
    void test() {
        this.client.mutateWith(jwt())
                .get()
                .uri("/api/test")
                .accept(MediaType.APPLICATION_JSON)
                .exchange()
                .expectStatus().isOk()
                .expectBody(String.class).isEqualTo("company");
    }

    private static SecurityMockServerConfigurers.JwtMutator jwt() {
        return mockJwt()
                .jwt(jwt -> jwt.claim("tenant", "company"));
    }
}
Alex
  • 4,987
  • 1
  • 8
  • 26
  • Hi @Alex, The above is working since it is in a controller. If you try the same within a graphql resolver I am getting an empty context – Siddharth Chaurasia Apr 24 '22 at 07:33
  • 1
    it looks like this known issue with this library. did you check https://github.com/graphql-java-kickstart/graphql-spring-boot/issues/437? – Alex Apr 24 '22 at 17:42