diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index 932fd097fb628..c6e0226ad6735 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -868,6 +868,16 @@
quarkus-oidc-client-reactive-filter-deployment
${project.version}
+
+ io.quarkus
+ quarkus-oidc-client-graphql
+ ${project.version}
+
+
+ io.quarkus
+ quarkus-oidc-client-graphql-deployment
+ ${project.version}
+
io.quarkus
quarkus-oidc-token-propagation
diff --git a/core/deployment/src/main/java/io/quarkus/deployment/Feature.java b/core/deployment/src/main/java/io/quarkus/deployment/Feature.java
index 6e8d59d8fd423..cebff1c71face 100644
--- a/core/deployment/src/main/java/io/quarkus/deployment/Feature.java
+++ b/core/deployment/src/main/java/io/quarkus/deployment/Feature.java
@@ -71,6 +71,7 @@ public enum Feature {
OIDC_CLIENT,
OIDC_CLIENT_FILTER,
OIDC_CLIENT_REACTIVE_FILTER,
+ OIDC_CLIENT_GRAPHQL_CLIENT_INTEGRATION,
OIDC_TOKEN_PROPAGATION,
OIDC_TOKEN_PROPAGATION_REACTIVE,
OPENSHIFT_CLIENT,
diff --git a/devtools/bom-descriptor-json/pom.xml b/devtools/bom-descriptor-json/pom.xml
index 22c2cca510f18..cbdbc050e4dfc 100644
--- a/devtools/bom-descriptor-json/pom.xml
+++ b/devtools/bom-descriptor-json/pom.xml
@@ -1565,6 +1565,19 @@
+
+ io.quarkus
+ quarkus-oidc-client-graphql
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
io.quarkus
quarkus-oidc-client-reactive-filter
diff --git a/docs/pom.xml b/docs/pom.xml
index d28aa46df1760..38070aa3d12c1 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -1581,6 +1581,19 @@
+
+ io.quarkus
+ quarkus-oidc-client-graphql-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
io.quarkus
quarkus-oidc-client-reactive-filter-deployment
diff --git a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc
index f875bc2f8931a..6e1ac30f88cd2 100644
--- a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc
+++ b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc
@@ -1117,6 +1117,62 @@ The `quarkus-oidc-token-propagation-reactive` extension provides `io.quarkus.oid
The `quarkus-oidc-token-propagation-reactive` extension (as opposed to the non-reactive `quarkus-oidc-token-propagation` extension) does not currently support the exchanging or resigning the tokens before the propagation.
However, these features may be added in the future.
+[[oidc-client-graphql-client]]
+== GraphQL client integration
+
+The `quarkus-oidc-client-graphql` extension provides a way to integrate an
+OIDC client into xref:smallrye-graphql-client.adoc[GraphQL clients]. This
+works similarly as with REST clients. When this extension is present, any
+configured (that means NOT created programmatically via the builder, but via
+configuration properties) GraphQL client will attempt to use the OIDC client
+to obtain an access token and set it as an `Authorization` header value.
+OIDC client will also refresh expired access tokens.
+
+To configure which OIDC client should be used by GraphQL client, select one of the configured OIDC clients with the `quarkus.oidc-client-graphql.client-name` property, for example:
+
+----
+quarkus.oidc-client-graphql.client-name=oidc-client-for-graphql
+
+# example declaration of the OIDC client itself
+quarkus.oidc-client.oidc-client-for-graphql.auth-server-url=http://example.com/realms/quarkus
+quarkus.oidc-client.annotation.grant-options.password.username=${username}
+quarkus.oidc-client.annotation.grant-options.password.password=${password}
+----
+
+NOTE: If you don't specify the `quarkus.oidc-client-graphql.client-name` property,
+GraphQL clients will use the default OIDC client (without an explicit name).
+
+Specifically for typesafe GraphQL clients, you can override this on a
+per-client basis by annotating the `GraphQLClientApi` interface with
+`@io.quarkus.oidc.client.filter.OidcClientFilter`. For example:
+
+[source,java]
+----
+@GraphQLClientApi(configKey = "order-client")
+@OidcClientFilter("oidc-client-for-graphql")
+public interface OrdersGraphQLClient {
+ // queries, mutations and subscriptions go here...
+}
+----
+
+To be able to use this with a programmatically created GraphQL client, both
+builders (`VertxDynamicGraphQLClientBuilder` and
+`VertxTypesafeGraphQLClientBuilder`) contain a method `dynamicHeader(String,
+Uni`) that allows you to plug in a header that might change for
+every request. To plug an OIDC client into it, use
+
+[source,java]
+----
+@Inject
+OidcClients oidcClients;
+
+VertxTypesafeGraphQLClientBuilder builder = ....;
+Uni tokenUni = oidcClients.getClient("OIDC_CLIENT_NAME")
+ .getTokens().map(t -> "Bearer " + t.getAccessToken());
+builder.dynamicHeader("Authorization", tokenUni);
+VertxDynamicGraphQLClient client = builder.build();
+----
+
== References
* xref:security-openid-connect-client.adoc[OpenID Connect Client and Token Propagation Quickstart]
diff --git a/docs/src/main/asciidoc/smallrye-graphql-client.adoc b/docs/src/main/asciidoc/smallrye-graphql-client.adoc
index aeb2c51ec46e9..417820fb770f1 100644
--- a/docs/src/main/asciidoc/smallrye-graphql-client.adoc
+++ b/docs/src/main/asciidoc/smallrye-graphql-client.adoc
@@ -346,3 +346,9 @@ formats it for better readability by humans, for example by piping the output th
This example showed how to use both the dynamic and typesafe GraphQL clients to call an external
GraphQL service and explained the difference between the client types.
+
+== References
+
+* xref:security-openid-connect-client-reference.adoc#oidc-client-graphql-client[Integrating OIDC clients into GraphQL clients]
+* https://smallrye.io/smallrye-graphql/latest/[Upstream SmallRye GraphQL Client documentation]
+
diff --git a/extensions/oidc-client-graphql/deployment/pom.xml b/extensions/oidc-client-graphql/deployment/pom.xml
new file mode 100644
index 0000000000000..ec964b1a93152
--- /dev/null
+++ b/extensions/oidc-client-graphql/deployment/pom.xml
@@ -0,0 +1,128 @@
+
+
+
+ quarkus-oidc-client-graphql-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../
+
+ 4.0.0
+
+ quarkus-oidc-client-graphql-deployment
+ Quarkus - OpenID Connect Client GraphQL integration - Deployment
+
+
+
+ io.quarkus
+ quarkus-core-deployment
+
+
+ io.quarkus
+ quarkus-arc-deployment
+
+
+ io.quarkus
+ quarkus-oidc-client-graphql
+
+
+ io.quarkus
+ quarkus-oidc-client-deployment
+
+
+ io.quarkus
+ quarkus-smallrye-graphql-client-deployment
+
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ io.quarkus
+ quarkus-test-keycloak-server
+ test
+
+
+ junit
+ junit
+
+
+
+
+ io.quarkus
+ quarkus-oidc-deployment
+ test
+
+
+ io.quarkus
+ quarkus-smallrye-graphql-deployment
+ test
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-deployment
+ test
+
+
+
+
+
+
+ src/test/resources
+ true
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+ maven-surefire-plugin
+
+ true
+
+
+
+
+
+
+ test-keycloak
+
+
+ test-containers
+
+
+
+
+
+ maven-surefire-plugin
+
+ false
+
+ ${keycloak.docker.legacy.image}
+ false
+
+
+
+
+
+
+
+
diff --git a/extensions/oidc-client-graphql/deployment/src/main/java/io/quarkus/oidc/client/graphql/OidcGraphQLClientIntegrationProcessor.java b/extensions/oidc-client-graphql/deployment/src/main/java/io/quarkus/oidc/client/graphql/OidcGraphQLClientIntegrationProcessor.java
new file mode 100644
index 0000000000000..5cc32a72e59c1
--- /dev/null
+++ b/extensions/oidc-client-graphql/deployment/src/main/java/io/quarkus/oidc/client/graphql/OidcGraphQLClientIntegrationProcessor.java
@@ -0,0 +1,57 @@
+package io.quarkus.oidc.client.graphql;
+
+import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jboss.jandex.AnnotationInstance;
+import org.jboss.jandex.AnnotationValue;
+import org.jboss.jandex.ClassInfo;
+import org.jboss.jandex.DotName;
+
+import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
+import io.quarkus.arc.deployment.BeanContainerBuildItem;
+import io.quarkus.deployment.Feature;
+import io.quarkus.deployment.annotations.BuildProducer;
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.annotations.Record;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.oidc.client.graphql.runtime.OidcClientGraphQLConfig;
+import io.quarkus.oidc.client.graphql.runtime.OidcGraphQLClientIntegrationRecorder;
+
+public class OidcGraphQLClientIntegrationProcessor {
+
+ private static final DotName GRAPHQL_CLIENT_API = DotName
+ .createSimple("io.smallrye.graphql.client.typesafe.api.GraphQLClientApi");
+
+ private static final String OIDC_CLIENT_FILTER = "io.quarkus.oidc.client.filter.OidcClientFilter";
+
+ @BuildStep
+ void feature(BuildProducer featureProducer) {
+ featureProducer.produce(new FeatureBuildItem(Feature.OIDC_CLIENT_GRAPHQL_CLIENT_INTEGRATION));
+ }
+
+ @BuildStep
+ @Record(RUNTIME_INIT)
+ void initialize(BeanContainerBuildItem containerBuildItem,
+ OidcGraphQLClientIntegrationRecorder recorder,
+ OidcClientGraphQLConfig config,
+ BeanArchiveIndexBuildItem index) {
+ Map configKeysToOidcClients = new HashMap<>();
+ for (AnnotationInstance annotation : index.getIndex().getAnnotations(GRAPHQL_CLIENT_API)) {
+ ClassInfo clazz = annotation.target().asClass();
+ AnnotationInstance oidcClient = clazz.annotation(OIDC_CLIENT_FILTER);
+ if (oidcClient != null) {
+ String oidcClientName = oidcClient.valueWithDefault(index.getIndex(), "value").asString();
+ AnnotationValue configKeyValue = annotation.value("configKey");
+ String configKey = configKeyValue != null ? configKeyValue.asString() : null;
+ String actualConfigKey = (configKey != null && !configKey.equals("")) ? configKey : clazz.name().toString();
+ if (oidcClientName != null && !oidcClientName.isEmpty()) {
+ configKeysToOidcClients.put(actualConfigKey, oidcClientName);
+ }
+ }
+ }
+ recorder.enhanceGraphQLClientConfigurationWithOidc(configKeysToOidcClients, config.clientName.orElse(null));
+ }
+}
diff --git a/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/AnnotationTypesafeGraphQLClient.java b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/AnnotationTypesafeGraphQLClient.java
new file mode 100644
index 0000000000000..e598ce5f6f50d
--- /dev/null
+++ b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/AnnotationTypesafeGraphQLClient.java
@@ -0,0 +1,15 @@
+package io.quarkus.oidc.client.graphql;
+
+import org.eclipse.microprofile.graphql.Query;
+
+import io.quarkus.oidc.client.filter.OidcClientFilter;
+import io.smallrye.graphql.client.typesafe.api.GraphQLClientApi;
+
+@GraphQLClientApi(configKey = "typesafe-annotation")
+@OidcClientFilter("annotation")
+public interface AnnotationTypesafeGraphQLClient {
+
+ @Query
+ String principalName();
+
+}
diff --git a/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/DefaultTypesafeGraphQLClient.java b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/DefaultTypesafeGraphQLClient.java
new file mode 100644
index 0000000000000..630c360e77bce
--- /dev/null
+++ b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/DefaultTypesafeGraphQLClient.java
@@ -0,0 +1,15 @@
+package io.quarkus.oidc.client.graphql;
+
+import org.eclipse.microprofile.graphql.Query;
+
+import io.quarkus.oidc.client.filter.OidcClientFilter;
+import io.smallrye.graphql.client.typesafe.api.GraphQLClientApi;
+
+@GraphQLClientApi(configKey = "typesafe-default")
+@OidcClientFilter
+public interface DefaultTypesafeGraphQLClient {
+
+ @Query
+ String principalName();
+
+}
diff --git a/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/GraphQLClientResource.java b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/GraphQLClientResource.java
new file mode 100644
index 0000000000000..5fdb09359dfea
--- /dev/null
+++ b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/GraphQLClientResource.java
@@ -0,0 +1,43 @@
+package io.quarkus.oidc.client.graphql;
+
+import java.util.concurrent.ExecutionException;
+
+import jakarta.inject.Inject;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+
+import io.smallrye.graphql.client.GraphQLClient;
+import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient;
+
+@Path("/oidc-graphql-client")
+public class GraphQLClientResource {
+
+ @Inject
+ AnnotationTypesafeGraphQLClient annotationTypesafeClient;
+
+ @Inject
+ DefaultTypesafeGraphQLClient defaultTypesafeClient;
+
+ @GET
+ @Path("/annotation-typesafe")
+ public String typesafeClientAnnotation() {
+ return annotationTypesafeClient.principalName();
+ }
+
+ @GET
+ @Path("/default-typesafe")
+ public String typesafeClientDefault() {
+ return defaultTypesafeClient.principalName();
+ }
+
+ @Inject
+ @GraphQLClient("default-dynamic")
+ DynamicGraphQLClient dynamicClient;
+
+ @GET
+ @Path("/default-dynamic")
+ public String dynamicClientDefault() throws ExecutionException, InterruptedException {
+ return dynamicClient.executeSync("query { principalName }").getData().getString("principalName");
+ }
+
+}
diff --git a/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/GraphQLClientUsingOidcClientTest.java b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/GraphQLClientUsingOidcClientTest.java
new file mode 100644
index 0000000000000..112fb47baf5cb
--- /dev/null
+++ b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/GraphQLClientUsingOidcClientTest.java
@@ -0,0 +1,62 @@
+package io.quarkus.oidc.client.graphql;
+
+import static org.hamcrest.Matchers.equalTo;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import io.quarkus.test.QuarkusDevModeTest;
+import io.quarkus.test.common.QuarkusTestResource;
+import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager;
+import io.restassured.RestAssured;
+
+@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class)
+public class GraphQLClientUsingOidcClientTest {
+
+ private static final Class>[] testClasses = {
+ ProtectedResource.class,
+ GraphQLClientResource.class,
+ AnnotationTypesafeGraphQLClient.class,
+ DefaultTypesafeGraphQLClient.class
+ };
+
+ @RegisterExtension
+ static final QuarkusDevModeTest test = new QuarkusDevModeTest()
+ .withApplicationRoot((jar) -> jar
+ .addClasses(testClasses)
+ .addAsResource("application.properties", "application.properties"));
+
+ @Test
+ public void typesafeClientAnnotation() {
+ // OidcClient selected via @OidcClient("clientName")
+ RestAssured.when().get("/oidc-graphql-client/annotation-typesafe")
+ .then()
+ .statusCode(200)
+ .body(equalTo("jdoe"));
+ }
+
+ @Test
+ public void typesafeClientWithDefault() {
+ // OidcClient selected via @OidcClient without a value - this should resort to the default client
+ RestAssured.when().get("/oidc-graphql-client/default-typesafe")
+ .then()
+ .statusCode(200)
+ .body(equalTo("alice"));
+ // just to make sure it works more than once
+ RestAssured.when().get("/oidc-graphql-client/default-typesafe")
+ .then()
+ .statusCode(200)
+ .body(equalTo("alice"));
+ }
+
+ @Test
+ public void dynamicClientWithDefault() {
+ // dynamic clients should always resort to the default (`quarkus.oidc-client-graphql.client-name`),
+ // because currently we don't have a way to override it
+ RestAssured.when().get("/oidc-graphql-client/default-dynamic")
+ .then()
+ .statusCode(200)
+ .body(equalTo("alice"));
+ }
+
+}
diff --git a/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/ProtectedResource.java b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/ProtectedResource.java
new file mode 100644
index 0000000000000..93f8bb23230ba
--- /dev/null
+++ b/extensions/oidc-client-graphql/deployment/src/test/java/io/quarkus/oidc/client/graphql/ProtectedResource.java
@@ -0,0 +1,23 @@
+package io.quarkus.oidc.client.graphql;
+
+import java.security.Principal;
+
+import jakarta.inject.Inject;
+
+import org.eclipse.microprofile.graphql.GraphQLApi;
+import org.eclipse.microprofile.graphql.Query;
+
+import io.quarkus.security.Authenticated;
+
+@GraphQLApi
+@Authenticated
+public class ProtectedResource {
+
+ @Inject
+ Principal principal;
+
+ @Query
+ public String principalName() {
+ return principal.getName();
+ }
+}
diff --git a/extensions/oidc-client-graphql/deployment/src/test/resources/application.properties b/extensions/oidc-client-graphql/deployment/src/test/resources/application.properties
new file mode 100644
index 0000000000000..721c8e9ab2883
--- /dev/null
+++ b/extensions/oidc-client-graphql/deployment/src/test/resources/application.properties
@@ -0,0 +1,27 @@
+quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/
+quarkus.oidc.client-id=quarkus-service-app
+quarkus.oidc.credentials.secret=secret
+
+quarkus.oidc-client.annotation.auth-server-url=${quarkus.oidc.auth-server-url}
+quarkus.oidc-client.annotation.client-id=${quarkus.oidc.client-id}
+quarkus.oidc-client.annotation.credentials.client-secret.value=${quarkus.oidc.credentials.secret}
+quarkus.oidc-client.annotation.credentials.client-secret.method=POST
+quarkus.oidc-client.annotation.grant.type=password
+quarkus.oidc-client.annotation.grant-options.password.username=jdoe
+quarkus.oidc-client.annotation.grant-options.password.password=jdoe
+
+quarkus.oidc-client.configured-default.auth-server-url=${quarkus.oidc.auth-server-url}
+quarkus.oidc-client.configured-default.client-id=${quarkus.oidc.client-id}
+quarkus.oidc-client.configured-default.credentials.client-secret.value=${quarkus.oidc.credentials.secret}
+quarkus.oidc-client.configured-default.credentials.client-secret.method=POST
+quarkus.oidc-client.configured-default.grant.type=password
+quarkus.oidc-client.configured-default.grant-options.password.username=alice
+quarkus.oidc-client.configured-default.grant-options.password.password=alice
+
+# FIXME: avoid hardcoding the URL
+quarkus.smallrye-graphql-client.typesafe-annotation.url=http://localhost:8080/graphql
+quarkus.smallrye-graphql-client.typesafe-default.url=http://localhost:8080/graphql
+quarkus.smallrye-graphql-client.default-dynamic.url=http://localhost:8080/graphql
+
+# default OIDC client for GraphQL clients
+quarkus.oidc-client-graphql.client-name=configured-default
\ No newline at end of file
diff --git a/extensions/oidc-client-graphql/pom.xml b/extensions/oidc-client-graphql/pom.xml
new file mode 100644
index 0000000000000..6f85166404778
--- /dev/null
+++ b/extensions/oidc-client-graphql/pom.xml
@@ -0,0 +1,20 @@
+
+
+
+ quarkus-extensions-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ quarkus-oidc-client-graphql-parent
+ Quarkus - OpenID Connect Client GraphQL integration
+ pom
+
+ deployment
+ runtime
+
+
diff --git a/extensions/oidc-client-graphql/runtime/pom.xml b/extensions/oidc-client-graphql/runtime/pom.xml
new file mode 100644
index 0000000000000..0fd99ca208623
--- /dev/null
+++ b/extensions/oidc-client-graphql/runtime/pom.xml
@@ -0,0 +1,60 @@
+
+
+
+ quarkus-oidc-client-graphql-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../
+
+ 4.0.0
+
+ quarkus-oidc-client-graphql
+ Quarkus - OpenID Connect Client GraphQL integration - Runtime
+ Use GraphQL client and get and refresh access tokens with OpenId Connect Client and send them as HTTP Authorization Bearer tokens
+
+
+ io.quarkus
+ quarkus-core
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-oidc-client
+
+
+ io.quarkus
+ quarkus-smallrye-graphql-client
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+
+
+
+
+ io.quarkus
+ quarkus-extension-maven-plugin
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/oidc-client-graphql/runtime/src/main/java/io/quarkus/oidc/client/graphql/runtime/OidcClientGraphQLConfig.java b/extensions/oidc-client-graphql/runtime/src/main/java/io/quarkus/oidc/client/graphql/runtime/OidcClientGraphQLConfig.java
new file mode 100644
index 0000000000000..7a78cd7137009
--- /dev/null
+++ b/extensions/oidc-client-graphql/runtime/src/main/java/io/quarkus/oidc/client/graphql/runtime/OidcClientGraphQLConfig.java
@@ -0,0 +1,18 @@
+package io.quarkus.oidc.client.graphql.runtime;
+
+import java.util.Optional;
+
+import io.quarkus.runtime.annotations.ConfigItem;
+import io.quarkus.runtime.annotations.ConfigPhase;
+import io.quarkus.runtime.annotations.ConfigRoot;
+
+@ConfigRoot(name = "oidc-client-graphql", phase = ConfigPhase.BUILD_AND_RUN_TIME_FIXED)
+public class OidcClientGraphQLConfig {
+
+ /**
+ * Name of the configured OidcClient used by GraphQL clients. You can override this configuration
+ * for typesafe clients with the `io.quarkus.oidc.client.filter.OidcClientFilter` annotation.
+ */
+ @ConfigItem
+ public Optional clientName;
+}
diff --git a/extensions/oidc-client-graphql/runtime/src/main/java/io/quarkus/oidc/client/graphql/runtime/OidcGraphQLClientIntegrationRecorder.java b/extensions/oidc-client-graphql/runtime/src/main/java/io/quarkus/oidc/client/graphql/runtime/OidcGraphQLClientIntegrationRecorder.java
new file mode 100644
index 0000000000000..38a8ebc9dcb6c
--- /dev/null
+++ b/extensions/oidc-client-graphql/runtime/src/main/java/io/quarkus/oidc/client/graphql/runtime/OidcGraphQLClientIntegrationRecorder.java
@@ -0,0 +1,44 @@
+package io.quarkus.oidc.client.graphql.runtime;
+
+import java.util.Map;
+
+import io.quarkus.arc.Arc;
+import io.quarkus.oidc.client.OidcClients;
+import io.quarkus.oidc.client.Tokens;
+import io.quarkus.runtime.annotations.Recorder;
+import io.smallrye.graphql.client.impl.GraphQLClientsConfiguration;
+import io.smallrye.mutiny.Uni;
+
+@Recorder
+public class OidcGraphQLClientIntegrationRecorder {
+
+ public void enhanceGraphQLClientConfigurationWithOidc(Map configKeysToOidcClients,
+ String defaultOidcClientName) {
+ OidcClients oidcClients = Arc.container().instance(OidcClients.class).get();
+ GraphQLClientsConfiguration configs = GraphQLClientsConfiguration.getInstance();
+ configs.getClients().forEach((graphQLClientKey, value) -> {
+ String oidcClient = configKeysToOidcClients.get(graphQLClientKey);
+ if (oidcClient == null) {
+ oidcClient = defaultOidcClientName;
+ }
+ Map> dynamicHeaders = configs.getClient(graphQLClientKey).getDynamicHeaders();
+ dynamicHeaders.put("Authorization", getToken(oidcClients, oidcClient));
+ });
+ }
+
+ public Uni getToken(OidcClients clients, String oidcClientId) {
+ Uni tokenUni = clients.getClient("MY_OIDC_CLIENT").getTokens().map(t -> "Bearer " + t.getAccessToken());
+ if (oidcClientId == null) {
+ return clients.getClient()
+ .getTokens()
+ .map(Tokens::getAccessToken)
+ .map(token -> "Bearer " + token);
+ } else {
+ return clients.getClient(oidcClientId)
+ .getTokens()
+ .map(Tokens::getAccessToken)
+ .map(token -> "Bearer " + token);
+ }
+ }
+
+}
diff --git a/extensions/oidc-client-graphql/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/oidc-client-graphql/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 0000000000000..33682cd9a1adf
--- /dev/null
+++ b/extensions/oidc-client-graphql/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,16 @@
+---
+artifact: ${project.groupId}:${project.artifactId}:${project.version}
+name: "OpenID Connect Client integration for GraphQL client"
+metadata:
+ keywords:
+ - "oauth2"
+ - "openid-connect"
+ - "oidc"
+ - "oidc-client"
+ - "graphql-client"
+ guide: "https://quarkus.io/guides/security-openid-connect-client"
+ categories:
+ - "security"
+ status: "experimental"
+ config:
+ - "quarkus.oidc-client."
diff --git a/extensions/pom.xml b/extensions/pom.xml
index 746699977266a..5734b8bde14df 100644
--- a/extensions/pom.xml
+++ b/extensions/pom.xml
@@ -139,6 +139,7 @@
oidc-client
oidc-client-filter
oidc-client-reactive-filter
+ oidc-client-graphql
oidc-token-propagation
oidc-token-propagation-reactive
oidc-db-token-state-manager
diff --git a/extensions/smallrye-graphql-client/deployment/src/main/java/io/quarkus/smallrye/graphql/client/deployment/SmallRyeGraphQLClientProcessor.java b/extensions/smallrye-graphql-client/deployment/src/main/java/io/quarkus/smallrye/graphql/client/deployment/SmallRyeGraphQLClientProcessor.java
index b74277457375d..7fec562a3f912 100644
--- a/extensions/smallrye-graphql-client/deployment/src/main/java/io/quarkus/smallrye/graphql/client/deployment/SmallRyeGraphQLClientProcessor.java
+++ b/extensions/smallrye-graphql-client/deployment/src/main/java/io/quarkus/smallrye/graphql/client/deployment/SmallRyeGraphQLClientProcessor.java
@@ -170,7 +170,8 @@ void initializeClientSupport(BuildProducer syntheticBean
shortNamesToQualifiedNames.put(clazz.name().withoutPackagePrefix(), clazz.name().toString());
AnnotationValue configKeyValue = annotation.value("configKey");
String configKey = configKeyValue != null ? configKeyValue.asString() : null;
- knownConfigKeys.add((configKey != null && !configKey.equals("")) ? configKey : clazz.name().toString());
+ String actualConfigKey = (configKey != null && !configKey.equals("")) ? configKey : clazz.name().toString();
+ knownConfigKeys.add(actualConfigKey);
}
for (AnnotationInstance annotation : index.getIndex().getAnnotations(GRAPHQL_CLIENT)) {
@@ -181,7 +182,8 @@ void initializeClientSupport(BuildProducer syntheticBean
knownConfigKeys.add(configKey);
}
- RuntimeValue support = recorder.clientSupport(shortNamesToQualifiedNames, knownConfigKeys);
+ RuntimeValue support = recorder.clientSupport(shortNamesToQualifiedNames,
+ knownConfigKeys);
DotName supportClassName = DotName.createSimple(GraphQLClientSupport.class.getName());
SyntheticBeanBuildItem bean = SyntheticBeanBuildItem
diff --git a/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java
index 840b244746a53..ac23a5b57b453 100644
--- a/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java
+++ b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java
@@ -115,6 +115,7 @@ private GraphQLClientConfiguration toSmallRyeNativeConfiguration(GraphQLClientCo
.ifPresent(transformed::setExecuteSingleOperationsOverWebsocket);
quarkusConfig.websocketInitializationTimeout.ifPresent(transformed::setWebsocketInitializationTimeout);
quarkusConfig.allowUnexpectedResponseFields.ifPresent(transformed::setAllowUnexpectedResponseFields);
+ transformed.setDynamicHeaders(new HashMap<>());
return transformed;
}