Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve OIDC client reference document #39367

Merged
merged 1 commit into from
Mar 13, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 163 additions & 42 deletions docs/src/main/asciidoc/security-openid-connect-client-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -157,48 +157,78 @@

==== Grant scopes

You might need to request that a specific set of scopes be associated with an issued access token.

Check warning on line 160 in docs/src/main/asciidoc/security-openid-connect-client-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/security-openid-connect-client-reference.adoc", "range": {"start": {"line": 160, "column": 11}}}, "severity": "INFO"}
Use a dedicated `quarkus.oidc-client.scopes` list property, for example: `quarkus.oidc-client.scopes=email,phone`

[[use-oidc-client-directly]]
=== Use OidcClient directly

Check warning on line 164 in docs/src/main/asciidoc/security-openid-connect-client-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Headings] Use sentence-style capitalization in 'Use OidcClient directly'. Raw Output: {"message": "[Quarkus.Headings] Use sentence-style capitalization in 'Use OidcClient directly'.", "location": {"path": "docs/src/main/asciidoc/security-openid-connect-client-reference.adoc", "range": {"start": {"line": 164, "column": 5}}}, "severity": "INFO"}

One can use `OidcClient` directly as follows:
You can use `OidcClient` directly to acquire access tokens and set them in an HTTP `Authorization` header as a `Bearer` scheme value.

Check warning on line 166 in docs/src/main/asciidoc/security-openid-connect-client-reference.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'.", "location": {"path": "docs/src/main/asciidoc/security-openid-connect-client-reference.adoc", "range": {"start": {"line": 166, "column": 107}}}, "severity": "INFO"}

For example, let's assume the Quarkus endpoint has to access a microservice that returns a user name.
First, create a REST client:

[source,java]
----
import jakarta.inject.PostConstruct;
import jakarta.inject.Inject;
package org.acme.security.openid.connect.client;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;

import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.HeaderParam;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;

@RegisterRestClient
@Path("/")
public interface RestClientWithTokenHeaderParam {

@GET
@Produces("text/plain")
@Path("userName")
Uni<String> getUserName(@HeaderParam("Authorization") String authorization);
}
----

Now, use `OidcClient` to acquire the tokens and propagate them:

[source,java]
----
package org.acme.security.openid.connect.client;

import org.eclipse.microprofile.rest.client.inject.RestClient;

import io.quarkus.oidc.client.runtime.TokensHelper;
import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.Tokens;

import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;

@Path("/service")
public class OidcClientResource {

@Inject
OidcClient client;
TokensHelper tokenHelper = new TokensHelper(); <1>

volatile Tokens currentTokens;

@PostConstruct
public void init() {
currentTokens = client.getTokens().await().indefinitely();
}
@Inject
@RestClient
RestClientWithTokenHeaderParam restClient;

@GET
public String getResponse() {

Tokens tokens = currentTokens;
if (tokens.isAccessTokenExpired()) {
// Add @Blocking method annotation if this code is used with the REST Client
tokens = client.refreshTokens(tokens.getRefreshToken()).await().indefinitely();
currentTokens = tokens;
}
// Use tokens.getAccessToken() to configure MP RestClient Authorization header/etc
@Path("user-name")
@Produces("text/plain")
public Uni<String> getUserName() {
return tokenHelper.getTokens(client).onItem()
.transformToUni(tokens -> restClient.getUserName("Bearer " + tokens.getAccessToken()));
}
}
----
<1> `io.quarkus.oidc.client.runtime.TokensHelper` manages the access token acquisition and refresh.

=== Inject tokens

Expand Down Expand Up @@ -244,30 +274,42 @@

[source,java]
----
import org.eclipse.microprofile.rest.client.inject.RestClient;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import io.smallrye.mutiny.Uni;
import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.OidcClients;
import io.quarkus.oidc.client.runtime.TokensHelper;

@Path("/clients")
public class OidcClientResource {

@Inject
OidcClients clients;
TokensHelper tokenHelper = new TokensHelper();

@Inject
@RestClient
RestClientWithTokenHeaderParam restClient; <1>

@GET
public String getResponse() {
OidcClient client = clients.getClient("jwt-secret");
//Use this client to get the token
@Path("user-name")
@Produces("text/plain")
public Uni<String> getUserName() {
OidcClient client = clients.getClient("jwt-secret");
return tokenHelper.getTokens(client).onItem()
.transformToUni(tokens -> restClient.getUserName("Bearer " + tokens.getAccessToken()));
}
}
----
<1> See the `RestClientWithTokenHeaderParam` declaration in the <<use-oidc-client-directly>> section.

[NOTE]
====
If you also use xref:security-openid-connect-multitenancy.adoc[OIDC multitenancy], and each OIDC tenant has its own associated `OidcClient`, you can use a Vert.x `RoutingContext` `tenantId` attribute. For example:
If you also use xref:security-openid-connect-multitenancy.adoc[OIDC multitenancy], and each OIDC tenant has its own associated `OidcClient`, you can use a Vert.x `RoutingContext` `tenant-id` attribute. For example:
sberyozkin marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If you also use xref:security-openid-connect-multitenancy.adoc[OIDC multitenancy], and each OIDC tenant has its own associated `OidcClient`, you can use a Vert.x `RoutingContext` `tenant-id` attribute. For example:
If you also use xref:security-openid-connect-multitenancy.adoc[OIDC multitenancy], and each OIDC tenant has its own associated `OidcClient`, you can use a Vert.x `RoutingContext` `tenant-id` attribute.
For example:


[source,java]
----
Expand All @@ -289,7 +331,7 @@

@GET
public String getResponse() {
String tenantId = context.get("tenantId");
String tenantId = context.get("tenant-id");
// named OIDC tenant and client configurations use the same key:
OidcClient client = clients.getClient(tenantId);
//Use this client to get the token
Expand All @@ -298,38 +340,93 @@
----
====

If you need, you can also create a new `OidcClient` programmatically like this:
You can also create a new `OidcClient` programmatically.
For example, let's assume you must create it at startup time:

[source,java]
----
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
package org.acme.security.openid.connect.client;

import java.util.Map;

import org.eclipse.microprofile.config.inject.ConfigProperty;

import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.OidcClients;
import io.quarkus.oidc.client.OidcClientConfig;
import io.quarkus.oidc.client.OidcClientConfig.Grant.Type;
import io.quarkus.oidc.client.OidcClients;
import io.quarkus.runtime.StartupEvent;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.i
nject.Inject;

@ApplicationScoped
public class OidcClientCreator {

@Inject
OidcClients oidcClients;
@ConfigProperty(name = "quarkus.oidc.auth-server-url")
String oidcProviderAddress;

private volatile OidcClient oidcClient;

public void startup(@Observes StartupEvent event) {
createOidcClient().subscribe().with(client -> {oidcClient = client;});
}

public OidcClient getOidcClient() {
return oidcClient;
}

private Uni<OidcClient> createOidcClient() {
OidcClientConfig cfg = new OidcClientConfig();
cfg.setId("myclient");
cfg.setAuthServerUrl(oidcProviderAddress);
cfg.setClientId("backend-service");
cfg.getCredentials().setSecret("secret");
cfg.getGrant().setType(Type.PASSWORD);
cfg.setGrantOptions(Map.of("password",
Map.of("username", "alice", "password", "alice")));
return oidcClients.newClient(cfg);
}
}
----

Now, you can use this client like this:

[source,java]
----
import org.eclipse.microprofile.rest.client.inject.RestClient;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import io.smallrye.mutiny.Uni;
import io.quarkus.oidc.client.runtime.TokensHelper;

@Path("/clients")
public class OidcClientResource {

@Inject
OidcClients clients;
OidcClientCreator clients;
TokensHelper tokenHelper = new TokensHelper();

@Inject
@RestClient
RestClientWithTokenHeaderParam restClient; <1>

@GET
public String getResponse() {
OidcClientConfig cfg = new OidcClientConfig();
cfg.setId("myclient");
cfg.setAuthServerUrl("http://localhost:8081/auth/realms/quarkus/");
cfg.setClientId("quarkus");
cfg.getCredentials().setSecret("secret");
Uni<OidcClient> client = clients.newClient(cfg);
//Use this client to get the token
@Path("user-name")
@Produces("text/plain")
public Uni<String> getUserName() {
return tokenHelper.getTokens(oidcClientCreator.getOidcClient()).onItem()
.transformToUni(tokens -> restClient.getUserName("Bearer " + tokens.getAccessToken()));
}
}
----
<1> See the `RestClientWithTokenHeaderParam` declaration in the <<use-oidc-client-directly>> section.

[[named-oidc-clients]]
=== Inject named OidcClient and tokens
Expand All @@ -338,11 +435,14 @@

[source,java]
----
package io.quarkus.oidc.client;
package org.acme.security.openid.connect.client;

import org.eclipse.microprofile.rest.client.inject.RestClient;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import io.smallrye.mutiny.Uni;
import io.quarkus.oidc.client.runtime.TokensHelper;

@Path("/clients")
public class OidcClientResource {
Expand All @@ -351,12 +451,22 @@
@NamedOidcClient("jwt-secret")
OidcClient client;

TokensHelper tokenHelper = new TokensHelper();

@Inject
@RestClient
RestClientWithTokenHeaderParam restClient; <1>

@GET
public String getResponse() {
//Use the client to get the token
@Path("user-name")
@Produces("text/plain")
public Uni<String> getUserName() {
return tokenHelper.getTokens(client).onItem()
.transformToUni(tokens -> restClient.getUserName("Bearer " + tokens.getAccessToken()));
}
}
----
<1> See the `RestClientWithTokenHeaderParam` declaration in the <<use-oidc-client-directly>> section.

The same qualifier can be used to specify the `OidcClient` used for a `Tokens` injection:

Expand Down Expand Up @@ -1283,6 +1393,17 @@
VertxDynamicGraphQLClient client = builder.build();
----

[[configuration-reference]]
== Configuration reference

=== OIDC client

include::{generated-dir}/config/quarkus-oidc-client.adoc[opts=optional, leveloffset=+1]

=== OIDC token propagation

include::{generated-dir}/config/quarkus-oidc-token-propagation-reactive.adoc[opts=optional, leveloffset=+1]

== References

* xref:security-openid-connect-client.adoc[OpenID Connect client and token propagation quickstart].
Expand Down
Loading