Skip to content

Commit e0ca7c6

Browse files
authored
Merge pull request quarkusio#36375 from jmartisk/graphql-client-oidc
OIDC client integration for GraphQL clients
2 parents 380795a + bd2beff commit e0ca7c6

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
@@ -1120,6 +1120,66 @@ The `quarkus-oidc-token-propagation-reactive` extension provides `io.quarkus.oid
11201120
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.
11211121
However, these features may be added in the future.
11221122

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

11251185
* 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
@@ -348,3 +348,9 @@ formats it for better readability by humans, for example by piping the output th
348348

349349
This example showed how to use both the dynamic and typesafe GraphQL clients to call an external
350350
GraphQL service and explained the difference between the client types.
351+
352+
== References
353+
354+
* xref:security-openid-connect-client-reference.adoc#oidc-client-graphql-client[Integrating OIDC clients into GraphQL clients]
355+
* https://smallrye.io/smallrye-graphql/latest/[Upstream SmallRye GraphQL Client documentation]
356+
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)