Accessing to an OAuth2 GraphQL server

Introduction

Since the 1.12 release, the generated code for the client side allows to use Spring. This also allows to use various Spring capabilities, like Spring Security, and easy configuration with Spring Boot.

To access to a GraphQL server protected by OAuth2, you'll need to create a basic Spring app, and this page describes how to add the OAuth2 capability to this basic Spring app.

This page details how to use Spring Boot to configure Spring Security. This allows to:

  • Get an OAuth token from an Authorization server
  • Access to a GraphQL server that is protected by OAuth, that is, a GraphQL server that expect to receive a token that can be validated against the above Autorization server.

This page won't detail more the OAuth protocol. You'll find more information on the web. And a good idea is to start by OAuth presentation.

Note for Jersey users, and old users of the plugin (prior to 1.12)

To connect to an OAuth server, you must use Spring capabilities. So the Jersey implementation of this plugin (prior to 1.11) can't do that.

If you're using the (now old) QueryExecutorImpl class, you won't be able to use this feature. Since the 1.12 release, the default QueryExecutor is the QueryExecutorSpringReactiveImpl which is based on Spring, with Spring reactive. Once the configuration below is done, this allows to access to OAuth protected urls for queries, mutations and subscriptions.

If you don't know if you're using QueryExecutorImpl class or not, that is, if we use the plugin runtime as is, then all this is transparent. By building with the graphql plugin in its 1.12 (or newer) version, then you'll use the QueryExecutorSpringReactiveImpl and all its capabilities. Including what's documented below.

Sample

This page is based on the graphql-maven-plugin-samples-allGraphQLCases-client sample.

This sample can be executed, when the servers below are started. They are available in the maven plugin project:

  • The OAuth2 Authorization server is in the graphql-maven-plugin-samples-OAuth-authorization-server module.
    • To start it, just execute the com.marcosbarbero.lab.sec.oauth.opaque.OAuth2OpaqueAuthorizationServerApp class, as a java application.
    • This server listen on the 8181 port. If you want to change it, you'll have to change its application.yml properties file, and the other application properties files accordingly.
  • The GraphQL server is in the graphql-maven-plugin-samples-allGraphQLCases-server module.
    • To start it, you'll have to build it, then execute the org.allGraphQLCases.server.util.GraphQLServerMain, generated by the graphql plugin in the target/generated-sources/graphql-maven-plugin folder.
    • This server listen on the 8180 port. If you want to change it, you'll have to change its application.yml properties file, and the other application properties files accordingly.

The http error codes

Here is an important notice about the HTTP status code that you may encounter here:

  • 401 means there is no OAuth authorization. There is an issue with the configuration, and probably no OAuth token is given to the resource server. Or the token is invalid.
  • 403 means that the resource server receives the OAuth authorization, and the token is valid. But for some reason, you're not allowed to access to this resource.

Some CURL command to check the OAuth configuration

As usual, it may happen (hum, ok, it always happens :) ) that some issue prevents your code to work as expected. So here are some useful commands, to check if the whole system is properly configured or not.

All these commands are based on the graphql-maven-plugin-samples-allGraphQLCases resource server, and the graphql-maven-plugin-samples-OAuth-authorization-server authorization server, as provided in the maven GraphQL plugin project.

This commands:

  • Are based on the curl command line tool.
  • The -i parameter allows to show the full response
  • The --noproxy "*" is necessary in my config, to avoid curl to send all these commands in the proxy, as the servers are local ones.
  • Contains tokens that you'll need to update, according to the one you'll get from the first request.

A sample query, to get an OAuth token:

curl -u "clientId:secret" -X POST "http://localhost:8181/oauth/token?grant_type=client_credentials" --noproxy "*" -i

Then, reuse the previous token in the next query:

curl -i -X POST "http://localhost:8180/graphql" --noproxy "*" -H "Authorization: Bearer 8c8e4a5b-d903-4ed6-9738-6f7f364b87ec"

And, to check the token:

curl -i -X GET "http://localhost:8181/profile/me" --noproxy "*" -H "Authorization: Bearer 8c8e4a5b-d903-4ed6-9738-6f7f364b87ec"

Enable the proper dependencies

Of course, in order to make all this work, you need some code in your classpath.

If you included com.graphql-java-generator:graphql-java-client-dependencies:pom, in your dependencies, everything should be ok. This includes this dependencies:

                <dependency>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-security</artifactId>
                </dependency>
                <dependency>
                        <groupId>org.springframework.security</groupId>
                        <artifactId>spring-security-oauth2-client</artifactId>
                </dependency>

Configure OAuth2 on client side, with Spring Boot

To configure OAuth2, there are tons of documentations on the web. But you should start from this sample. Below is the application.properties file of the graphql-maven-plugin-samples-allGraphQLCases-client module. This file must on the root of the resource folder. Here it is:

graphql.endpoint.url = http://localhost:8180/graphql
graphql.endpoint.subscriptionUrl = http://localhost:8180/graphql/subscription

# We don't need the Netty web server to start
spring.main.web-application-type = none



# Configuration for OAuth2, for our local OAuth authorization server
spring.security.oauth2.client.registration.provider_test.authorization-grant-type=client_credentials
spring.security.oauth2.client.registration.provider_test.client-id=clientId
spring.security.oauth2.client.registration.provider_test.client-secret=secret

spring.security.oauth2.client.provider.provider_test.token-uri=http://localhost:8181/oauth/token

The first part is the GraphQL configuration part.

The second part deals with OAuth:

  • This sample uses the client_credentials OAuth grant type.
  • We're using the provider_test OAuth provider. You find the provider's name in each spring configuration properties. It is given to spring when defining the serverOAuth2AuthorizedClientExchangeFilterFunction Spring bean (see below)

You'll find tons of docs on the web, on how to configure the Spring client for other grant types. All the job is done in this configuration file. There should be no impact on the code, out of defining the ServerOAuth2AuthorizedClientExchangeFilterFunction as explained below.

Enable OAuth2 on client side, with Spring Boot

We're starting with the basic Spring app.

Adding the OAuth2 capability is as simple as adding the ServerOAuth2AuthorizedClientExchangeFilterFunction, like in the sample below:

@SpringBootApplication(scanBasePackageClasses = { MinimalOAuthApp.class, GraphQLConfiguration.class, MyQueryTypeExecutor.class })
public class MinimalOAuthApp implements CommandLineRunner {

        @Autowired
        MyQueryTypeExecutor queryType;

        public static void main(String[] args) {
                SpringApplication.run(MinimalOAuthApp.class, args);
        }

        /**
         * This method is started by Spring, once the Spring context has been loaded. This is run, as this class implements
         * {@link CommandLineRunner}
         */
        @Override
        public void run(String... args) throws Exception {
                String query = "{appearsIn name }";
                System.out.println("Executing this query: '" + query + "'");
                System.out.println(queryType.withoutParameters(query));
        }

        @Bean
        ServerOAuth2AuthorizedClientExchangeFilterFunction serverOAuth2AuthorizedClientExchangeFilterFunction(
                        ReactiveClientRegistrationRepository clientRegistrations) {
                ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
                                clientRegistrations, new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
                oauth.setDefaultClientRegistrationId("provider_test");
                return oauth;
        }
}

What happened there?

Just some Spring magic... ! :)

The serverOAuth2AuthorizedClientExchangeFilterFunction is marked as Spring bean (same as the @Component on a class). So its auto loaded into the Spring's beans container. This tells Spring Boot to configure Spring Securities.

The serverOAuth2AuthorizedClientExchangeFilterFunction is responsible for each subsequence http request to:

  • Check that there is a valid OAuth2 token
    • If there is no token, a request to the OAuth authorization server is issued
    • If there is an expired token, a refresh request is issued
  • Add the relevant header into the http request

The important point, here, is to define the provider's name as it is named in the application.properties file. In this sample, its name is provider_test.