Skip to content

Commit 8723151

Browse files
authored
OIDC Client credentials flow fix (helidon-io#10709)
Client Credentials flow fix Signed-off-by: David Kral <david.k.kral@oracle.com>
1 parent 0d5210c commit 8723151

4 files changed

Lines changed: 110 additions & 3 deletions

File tree

security/providers/oidc-common/pom.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@
129129
<artifactId>helidon-config-metadata-codegen</artifactId>
130130
<version>${helidon.version}</version>
131131
</path>
132+
<path>
133+
<groupId>io.helidon.builder</groupId>
134+
<artifactId>helidon-builder-codegen</artifactId>
135+
<version>${helidon.version}</version>
136+
</path>
137+
<path>
138+
<groupId>io.helidon.codegen</groupId>
139+
<artifactId>helidon-codegen-helidon-copyright</artifactId>
140+
<version>${helidon.version}</version>
141+
</path>
132142
</annotationProcessorPaths>
133143
</configuration>
134144
<dependencies>
@@ -142,6 +152,16 @@
142152
<artifactId>helidon-config-metadata-codegen</artifactId>
143153
<version>${helidon.version}</version>
144154
</dependency>
155+
<dependency>
156+
<groupId>io.helidon.builder</groupId>
157+
<artifactId>helidon-builder-codegen</artifactId>
158+
<version>${helidon.version}</version>
159+
</dependency>
160+
<dependency>
161+
<groupId>io.helidon.codegen</groupId>
162+
<artifactId>helidon-codegen-helidon-copyright</artifactId>
163+
<version>${helidon.version}</version>
164+
</dependency>
145165
</dependencies>
146166
</plugin>
147167
</plugins>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2018, 2025 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.security.providers.oidc.common;
18+
19+
import java.util.Optional;
20+
21+
import io.helidon.builder.api.Option;
22+
import io.helidon.builder.api.Prototype;
23+
24+
/**
25+
* Configuration of the OIDC client credentials flow.
26+
*/
27+
@Prototype.Blueprint
28+
@Prototype.Configured
29+
interface ClientCredentialsConfigBlueprint {
30+
31+
/**
32+
* Scope used when obtaining access token in the client credentials flow.
33+
* It is required to set when {@code server-type} is set to {@code idcs}.
34+
*
35+
* @return client credentials scope
36+
*/
37+
@Option.Configured
38+
Optional<String> scope();
39+
40+
}

security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/OidcConfig.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ public final class OidcConfig extends TenantConfigImpl {
414414
private final boolean pkceEnabled;
415415
private final PkceChallengeMethod pkceChallengeMethod;
416416
private final OidcOutboundType outboundType;
417+
private final ClientCredentialsConfig clientCredentialsConfig;
417418

418419
private OidcConfig(Builder builder) {
419420
super(builder);
@@ -453,6 +454,7 @@ private OidcConfig(Builder builder) {
453454
this.webClientBuilderSupplier = builder.webClientBuilderSupplier;
454455
this.defaultTenant = LazyValue.create(() -> Tenant.create(this, this));
455456
this.outboundType = builder.outboundType;
457+
this.clientCredentialsConfig = builder.clientCredentialsConfig;
456458

457459
LOGGER.log(Level.TRACE, () -> "Redirect URI with host: " + frontendUri + redirectUri);
458460
}
@@ -871,6 +873,15 @@ public OidcOutboundType outboundType() {
871873
return outboundType;
872874
}
873875

876+
/**
877+
* Client credentials configuration.
878+
*
879+
* @return client credentials config
880+
*/
881+
public ClientCredentialsConfig clientCredentialsConfig() {
882+
return clientCredentialsConfig;
883+
}
884+
874885
Supplier<WebClientConfig.Builder> webClientBuilderSupplier() {
875886
return webClientBuilderSupplier;
876887
}
@@ -988,6 +999,7 @@ public static class Builder extends BaseBuilder<Builder, OidcConfig> {
988999
private boolean useHeader = DEFAULT_HEADER_USE;
9891000
private boolean useParam = DEFAULT_PARAM_USE;
9901001
private OidcOutboundType outboundType = OidcOutboundType.USER_JWT;
1002+
private ClientCredentialsConfig clientCredentialsConfig = ClientCredentialsConfig.create();
9911003
private boolean pkceEnabled = DEFAULT_PKCE_ENABLED;
9921004
private PkceChallengeMethod pkceChallengeMethod = PkceChallengeMethod.S256;
9931005

@@ -1037,6 +1049,13 @@ public OidcConfig build() {
10371049
collector.fatal("post-logout-uri must be defined when logout is enabled.");
10381050
}
10391051
}
1052+
if (outboundType == OidcOutboundType.CLIENT_CREDENTIALS) {
1053+
if (clientCredentialsConfig.scope().isEmpty()
1054+
&& serverType().equals("idcs")) {
1055+
collector.fatal("client-credential.scope must be defined when client credentials flow "
1056+
+ "is set as an outbound type and \"idcs\" is the server type");
1057+
}
1058+
}
10401059

10411060
// second set of validations
10421061
collector.collect().checkValid();
@@ -1172,6 +1191,8 @@ public Builder config(Config config) {
11721191
.ifPresent(confList -> confList.forEach(tenantConfig -> tenantFromConfig(config, tenantConfig)));
11731192

11741193
config.get("outbound-type").as(OidcOutboundType.class).ifPresent(this::outboundType);
1194+
config.get("client-credentials").as(Config.class)
1195+
.ifPresent(it -> clientCredentialsConfig(ClientCredentialsConfig.create(it)));
11751196
config.get("pkce-enabled").asBoolean().ifPresent(this::pkceEnabled);
11761197
config.get("pkce-challenge-method").as(PkceChallengeMethod.class).ifPresent(this::pkceChallengeMethod);
11771198

@@ -1906,5 +1927,31 @@ public Builder clientTimeout(Duration duration) {
19061927
webClientConfigBuilder.socketOptions(newSocketBuilder);
19071928
return this;
19081929
}
1930+
1931+
/**
1932+
* Set the configuration related to the client credentials flow.
1933+
*
1934+
* @param clientCredentialsConfig client credentials configuration
1935+
* @return updated builder instance
1936+
*/
1937+
@ConfiguredOption
1938+
public Builder clientCredentialsConfig(ClientCredentialsConfig clientCredentialsConfig) {
1939+
this.clientCredentialsConfig = Objects.requireNonNull(clientCredentialsConfig);
1940+
return this;
1941+
}
1942+
1943+
/**
1944+
* Configure client credentials configuration over the builder consumer.
1945+
*
1946+
* @param builderConsumer builder consumer
1947+
* @return updated builder instance
1948+
*/
1949+
public Builder clientCredentialsConfig(Consumer<ClientCredentialsConfig.Builder> builderConsumer) {
1950+
var builder = ClientCredentialsConfig.builder();
1951+
builderConsumer.accept(builder);
1952+
this.clientCredentialsConfig = builder.build();
1953+
return this;
1954+
}
1955+
19091956
}
19101957
}

security/providers/oidc/src/main/java/io/helidon/security/providers/oidc/OidcProvider.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import io.helidon.security.providers.common.OutboundConfig;
4848
import io.helidon.security.providers.common.OutboundTarget;
4949
import io.helidon.security.providers.common.TokenCredential;
50+
import io.helidon.security.providers.oidc.common.ClientCredentialsConfig;
5051
import io.helidon.security.providers.oidc.common.OidcConfig;
5152
import io.helidon.security.providers.oidc.common.Tenant;
5253
import io.helidon.security.providers.oidc.common.TenantConfig;
@@ -249,12 +250,11 @@ private OutboundSecurityResponse clientCredentials(ProviderRequest providerReque
249250
OidcOutboundTarget target = outboundConfig.findTarget(outboundEnv);
250251
boolean enabled = target.propagate;
251252
if (enabled) {
253+
ClientCredentialsConfig clientCredentialsConfig = oidcConfig.clientCredentialsConfig();
252254
Parameters.Builder formBuilder = Parameters.builder("oidc-form-params")
253255
.add("grant_type", "client_credentials");
254256

255-
if (!oidcConfig.baseScopes().isEmpty()) {
256-
formBuilder.add("scope", oidcConfig.baseScopes());
257-
}
257+
clientCredentialsConfig.scope().ifPresent(scope -> formBuilder.add("scope", scope));
258258

259259
HttpClientRequest postRequest = oidcConfig.appWebClient()
260260
.post()

0 commit comments

Comments
 (0)