Skip to content

Commit bd2beff

Browse files
committed
OIDC client integration for GraphQL clients
1 parent 61d3986 commit bd2beff

File tree

22 files changed

+636
-2
lines changed

22 files changed

+636
-2
lines changed

bom/application/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,16 @@
868868
<artifactId>quarkus-oidc-client-reactive-filter-deployment</artifactId>
869869
<version>${project.version}</version>
870870
</dependency>
871+
<dependency>
872+
<groupId>io.quarkus</groupId>
873+
<artifactId>quarkus-oidc-client-graphql</artifactId>
874+
<version>${project.version}</version>
875+
</dependency>
876+
<dependency>
877+
<groupId>io.quarkus</groupId>
878+
<artifactId>quarkus-oidc-client-graphql-deployment</artifactId>
879+
<version>${project.version}</version>
880+
</dependency>
871881
<dependency>
872882
<groupId>io.quarkus</groupId>
873883
<artifactId>quarkus-oidc-token-propagation</artifactId>

core/deployment/src/main/java/io/quarkus/deployment/Feature.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public enum Feature {
7171
OIDC_CLIENT,
7272
OIDC_CLIENT_FILTER,
7373
OIDC_CLIENT_REACTIVE_FILTER,
74+
OIDC_CLIENT_GRAPHQL_CLIENT_INTEGRATION,
7475
OIDC_TOKEN_PROPAGATION,
7576
OIDC_TOKEN_PROPAGATION_REACTIVE,
7677
OPENSHIFT_CLIENT,

devtools/bom-descriptor-json/pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,6 +1565,19 @@
15651565
</exclusion>
15661566
</exclusions>
15671567
</dependency>
1568+
<dependency>
1569+
<groupId>io.quarkus</groupId>
1570+
<artifactId>quarkus-oidc-client-graphql</artifactId>
1571+
<version>${project.version}</version>
1572+
<type>pom</type>
1573+
<scope>test</scope>
1574+
<exclusions>
1575+
<exclusion>
1576+
<groupId>*</groupId>
1577+
<artifactId>*</artifactId>
1578+
</exclusion>
1579+
</exclusions>
1580+
</dependency>
15681581
<dependency>
15691582
<groupId>io.quarkus</groupId>
15701583
<artifactId>quarkus-oidc-client-reactive-filter</artifactId>

docs/pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,6 +1581,19 @@
15811581
</exclusion>
15821582
</exclusions>
15831583
</dependency>
1584+
<dependency>
1585+
<groupId>io.quarkus</groupId>
1586+
<artifactId>quarkus-oidc-client-graphql-deployment</artifactId>
1587+
<version>${project.version}</version>
1588+
<type>pom</type>
1589+
<scope>test</scope>
1590+
<exclusions>
1591+
<exclusion>
1592+
<groupId>*</groupId>
1593+
<artifactId>*</artifactId>
1594+
</exclusion>
1595+
</exclusions>
1596+
</dependency>
15841597
<dependency>
15851598
<groupId>io.quarkus</groupId>
15861599
<artifactId>quarkus-oidc-client-reactive-filter-deployment</artifactId>

docs/src/main/asciidoc/security-openid-connect-client-reference.adoc

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,6 +1117,66 @@ The `quarkus-oidc-token-propagation-reactive` extension provides `io.quarkus.oid
11171117
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.
11181118
However, these features may be added in the future.
11191119

1120+
[[oidc-client-graphql-client]]
1121+
== GraphQL client integration
1122+
1123+
The `quarkus-oidc-client-graphql` extension provides a way to integrate an
1124+
OIDC client into xref:smallrye-graphql-client.adoc[GraphQL clients]. This
1125+
works similarly as with REST clients. When this extension is present, any
1126+
configured (that means NOT created programmatically via the builder, but via
1127+
configuration properties) GraphQL client will attempt to use the OIDC client
1128+
to obtain an access token and set it as an `Authorization` header value.
1129+
OIDC client will also refresh expired access tokens.
1130+
1131+
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:
1132+
1133+
----
1134+
quarkus.oidc-client-graphql.client-name=oidc-client-for-graphql
1135+
1136+
# example declaration of the OIDC client itself
1137+
quarkus.oidc-client.oidc-client-for-graphql.auth-server-url=${keycloak.url}
1138+
quarkus.oidc-client.oidc-client-for-graphql.grant.type=password
1139+
quarkus.oidc-client.oidc-client-for-graphql.grant-options.password.username=${username}
1140+
quarkus.oidc-client.oidc-client-for-graphql.grant-options.password.password=${password}
1141+
quarkus.oidc-client.oidc-client-for-graphql.client-id=${quarkus.oidc.client-id}
1142+
quarkus.oidc-client.oidc-client-for-graphql.credentials.client-secret.value=${keycloak.credentials.secret}
1143+
quarkus.oidc-client.oidc-client-for-graphql.credentials.client-secret.method=POST
1144+
----
1145+
1146+
NOTE: If you don't specify the `quarkus.oidc-client-graphql.client-name` property,
1147+
GraphQL clients will use the default OIDC client (without an explicit name).
1148+
1149+
Specifically for typesafe GraphQL clients, you can override this on a
1150+
per-client basis by annotating the `GraphQLClientApi` interface with
1151+
`@io.quarkus.oidc.client.filter.OidcClientFilter`. For example:
1152+
1153+
[source,java]
1154+
----
1155+
@GraphQLClientApi(configKey = "order-client")
1156+
@OidcClientFilter("oidc-client-for-graphql")
1157+
public interface OrdersGraphQLClient {
1158+
// queries, mutations and subscriptions go here...
1159+
}
1160+
----
1161+
1162+
To be able to use this with a programmatically created GraphQL client, both
1163+
builders (`VertxDynamicGraphQLClientBuilder` and
1164+
`VertxTypesafeGraphQLClientBuilder`) contain a method `dynamicHeader(String,
1165+
Uni<String>`) that allows you to plug in a header that might change for
1166+
every request. To plug an OIDC client into it, use
1167+
1168+
[source,java]
1169+
----
1170+
@Inject
1171+
OidcClients oidcClients;
1172+
1173+
VertxTypesafeGraphQLClientBuilder builder = ....;
1174+
Uni<String> tokenUni = oidcClients.getClient("OIDC_CLIENT_NAME")
1175+
.getTokens().map(t -> "Bearer " + t.getAccessToken());
1176+
builder.dynamicHeader("Authorization", tokenUni);
1177+
VertxDynamicGraphQLClient client = builder.build();
1178+
----
1179+
11201180
== References
11211181

11221182
* xref:security-openid-connect-client.adoc[OpenID Connect Client and Token Propagation Quickstart]

docs/src/main/asciidoc/smallrye-graphql-client.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,3 +346,9 @@ formats it for better readability by humans, for example by piping the output th
346346

347347
This example showed how to use both the dynamic and typesafe GraphQL clients to call an external
348348
GraphQL service and explained the difference between the client types.
349+
350+
== References
351+
352+
* xref:security-openid-connect-client-reference.adoc#oidc-client-graphql-client[Integrating OIDC clients into GraphQL clients]
353+
* https://smallrye.io/smallrye-graphql/latest/[Upstream SmallRye GraphQL Client documentation]
354+
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<parent>
6+
<artifactId>quarkus-oidc-client-graphql-parent</artifactId>
7+
<groupId>io.quarkus</groupId>
8+
<version>999-SNAPSHOT</version>
9+
<relativePath>../</relativePath>
10+
</parent>
11+
<modelVersion>4.0.0</modelVersion>
12+
13+
<artifactId>quarkus-oidc-client-graphql-deployment</artifactId>
14+
<name>Quarkus - OpenID Connect Client GraphQL integration - Deployment</name>
15+
16+
<dependencies>
17+
<dependency>
18+
<groupId>io.quarkus</groupId>
19+
<artifactId>quarkus-core-deployment</artifactId>
20+
</dependency>
21+
<dependency>
22+
<groupId>io.quarkus</groupId>
23+
<artifactId>quarkus-arc-deployment</artifactId>
24+
</dependency>
25+
<dependency>
26+
<groupId>io.quarkus</groupId>
27+
<artifactId>quarkus-oidc-client-graphql</artifactId>
28+
</dependency>
29+
<dependency>
30+
<groupId>io.quarkus</groupId>
31+
<artifactId>quarkus-oidc-client-deployment</artifactId>
32+
</dependency>
33+
<dependency>
34+
<groupId>io.quarkus</groupId>
35+
<artifactId>quarkus-smallrye-graphql-client-deployment</artifactId>
36+
</dependency>
37+
<!-- Test dependencies -->
38+
<dependency>
39+
<groupId>io.quarkus</groupId>
40+
<artifactId>quarkus-junit5-internal</artifactId>
41+
<scope>test</scope>
42+
</dependency>
43+
<dependency>
44+
<groupId>io.rest-assured</groupId>
45+
<artifactId>rest-assured</artifactId>
46+
<scope>test</scope>
47+
</dependency>
48+
<dependency>
49+
<groupId>io.quarkus</groupId>
50+
<artifactId>quarkus-test-keycloak-server</artifactId>
51+
<scope>test</scope>
52+
<exclusions>
53+
<exclusion>
54+
<groupId>junit</groupId>
55+
<artifactId>junit</artifactId>
56+
</exclusion>
57+
</exclusions>
58+
</dependency>
59+
<dependency>
60+
<groupId>io.quarkus</groupId>
61+
<artifactId>quarkus-oidc-deployment</artifactId>
62+
<scope>test</scope>
63+
</dependency>
64+
<dependency>
65+
<groupId>io.quarkus</groupId>
66+
<artifactId>quarkus-smallrye-graphql-deployment</artifactId>
67+
<scope>test</scope>
68+
</dependency>
69+
<dependency>
70+
<groupId>io.quarkus</groupId>
71+
<artifactId>quarkus-resteasy-reactive-deployment</artifactId>
72+
<scope>test</scope>
73+
</dependency>
74+
</dependencies>
75+
76+
<build>
77+
<testResources>
78+
<testResource>
79+
<directory>src/test/resources</directory>
80+
<filtering>true</filtering>
81+
</testResource>
82+
</testResources>
83+
<plugins>
84+
<plugin>
85+
<artifactId>maven-compiler-plugin</artifactId>
86+
<configuration>
87+
<annotationProcessorPaths>
88+
<path>
89+
<groupId>io.quarkus</groupId>
90+
<artifactId>quarkus-extension-processor</artifactId>
91+
<version>${project.version}</version>
92+
</path>
93+
</annotationProcessorPaths>
94+
</configuration>
95+
</plugin>
96+
<plugin>
97+
<artifactId>maven-surefire-plugin</artifactId>
98+
<configuration>
99+
<skip>true</skip>
100+
</configuration>
101+
</plugin>
102+
</plugins>
103+
</build>
104+
<profiles>
105+
<profile>
106+
<id>test-keycloak</id>
107+
<activation>
108+
<property>
109+
<name>test-containers</name>
110+
</property>
111+
</activation>
112+
<build>
113+
<plugins>
114+
<plugin>
115+
<artifactId>maven-surefire-plugin</artifactId>
116+
<configuration>
117+
<skip>false</skip>
118+
<systemPropertyVariables>
119+
<keycloak.docker.image>${keycloak.docker.legacy.image}</keycloak.docker.image>
120+
<keycloak.use.https>false</keycloak.use.https>
121+
</systemPropertyVariables>
122+
</configuration>
123+
</plugin>
124+
</plugins>
125+
</build>
126+
</profile>
127+
</profiles>
128+
</project>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package io.quarkus.oidc.client.graphql;
2+
3+
import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
4+
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
8+
import org.jboss.jandex.AnnotationInstance;
9+
import org.jboss.jandex.AnnotationValue;
10+
import org.jboss.jandex.ClassInfo;
11+
import org.jboss.jandex.DotName;
12+
13+
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
14+
import io.quarkus.arc.deployment.BeanContainerBuildItem;
15+
import io.quarkus.deployment.Feature;
16+
import io.quarkus.deployment.annotations.BuildProducer;
17+
import io.quarkus.deployment.annotations.BuildStep;
18+
import io.quarkus.deployment.annotations.Record;
19+
import io.quarkus.deployment.builditem.FeatureBuildItem;
20+
import io.quarkus.oidc.client.graphql.runtime.OidcClientGraphQLConfig;
21+
import io.quarkus.oidc.client.graphql.runtime.OidcGraphQLClientIntegrationRecorder;
22+
23+
public class OidcGraphQLClientIntegrationProcessor {
24+
25+
private static final DotName GRAPHQL_CLIENT_API = DotName
26+
.createSimple("io.smallrye.graphql.client.typesafe.api.GraphQLClientApi");
27+
28+
private static final String OIDC_CLIENT_FILTER = "io.quarkus.oidc.client.filter.OidcClientFilter";
29+
30+
@BuildStep
31+
void feature(BuildProducer<FeatureBuildItem> featureProducer) {
32+
featureProducer.produce(new FeatureBuildItem(Feature.OIDC_CLIENT_GRAPHQL_CLIENT_INTEGRATION));
33+
}
34+
35+
@BuildStep
36+
@Record(RUNTIME_INIT)
37+
void initialize(BeanContainerBuildItem containerBuildItem,
38+
OidcGraphQLClientIntegrationRecorder recorder,
39+
OidcClientGraphQLConfig config,
40+
BeanArchiveIndexBuildItem index) {
41+
Map<String, String> configKeysToOidcClients = new HashMap<>();
42+
for (AnnotationInstance annotation : index.getIndex().getAnnotations(GRAPHQL_CLIENT_API)) {
43+
ClassInfo clazz = annotation.target().asClass();
44+
AnnotationInstance oidcClient = clazz.annotation(OIDC_CLIENT_FILTER);
45+
if (oidcClient != null) {
46+
String oidcClientName = oidcClient.valueWithDefault(index.getIndex(), "value").asString();
47+
AnnotationValue configKeyValue = annotation.value("configKey");
48+
String configKey = configKeyValue != null ? configKeyValue.asString() : null;
49+
String actualConfigKey = (configKey != null && !configKey.equals("")) ? configKey : clazz.name().toString();
50+
if (oidcClientName != null && !oidcClientName.isEmpty()) {
51+
configKeysToOidcClients.put(actualConfigKey, oidcClientName);
52+
}
53+
}
54+
}
55+
recorder.enhanceGraphQLClientConfigurationWithOidc(configKeysToOidcClients, config.clientName.orElse(null));
56+
}
57+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.quarkus.oidc.client.graphql;
2+
3+
import org.eclipse.microprofile.graphql.Query;
4+
5+
import io.quarkus.oidc.client.filter.OidcClientFilter;
6+
import io.smallrye.graphql.client.typesafe.api.GraphQLClientApi;
7+
8+
@GraphQLClientApi(configKey = "typesafe-annotation")
9+
@OidcClientFilter("annotation")
10+
public interface AnnotationTypesafeGraphQLClient {
11+
12+
@Query
13+
String principalName();
14+
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.quarkus.oidc.client.graphql;
2+
3+
import org.eclipse.microprofile.graphql.Query;
4+
5+
import io.quarkus.oidc.client.filter.OidcClientFilter;
6+
import io.smallrye.graphql.client.typesafe.api.GraphQLClientApi;
7+
8+
@GraphQLClientApi(configKey = "typesafe-default")
9+
@OidcClientFilter
10+
public interface DefaultTypesafeGraphQLClient {
11+
12+
@Query
13+
String principalName();
14+
15+
}

0 commit comments

Comments
 (0)