第 1 章 OpenID Connect (OIDC) and OAuth2 client and filters


You can use Quarkus extensions for OpenID Connect and OAuth 2.0 access token management, focusing on acquiring, refreshing, and propagating tokens.

This includes the following:

  • Using quarkus-oidc-client, quarkus-rest-client-oidc-filter and quarkus-resteasy-client-oidc-filter extensions to acquire and refresh access tokens from OpenID Connect and OAuth 2.0 compliant Authorization Servers such as Keycloak.
  • Using quarkus-rest-client-oidc-token-propagation and quarkus-resteasy-client-oidc-token-propagation extensions to propagate the current Bearer or Authorization Code Flow access tokens.

The access tokens managed by these extensions can be used as HTTP Authorization Bearer tokens to access the remote services.

Also see OpenID Connect client and token propagation quickstart.

1.1. OidcClient

Add the following dependency:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-oidc-client</artifactId>
</dependency>

The quarkus-oidc-client extension provides a reactive io.quarkus.oidc.client.OidcClient, which can be used to acquire and refresh tokens using SmallRye Mutiny Uni and Vert.x WebClient.

OidcClient is initialized at build time with the IDP token endpoint URL, which can be auto-discovered or manually configured. OidcClient uses this endpoint to acquire access tokens by using token grants such as client_credentials or password and refresh the tokens by using a refresh_token grant.

1.1.1. Token endpoint configuration

By default, the token endpoint address is discovered by adding a /.well-known/openid-configuration path to the configured quarkus.oidc-client.auth-server-url.

For example, given this Keycloak URL:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus

OidcClient will discover that the token endpoint URL is http://localhost:8180/auth/realms/quarkus/protocol/openid-connect/tokens.

Alternatively, if the discovery endpoint is unavailable or you want to save on the discovery endpoint round-trip, you can disable the discovery and configure the token endpoint address with a relative path value. For example:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus
quarkus.oidc-client.discovery-enabled=false
# Token endpoint: http://localhost:8180/auth/realms/quarkus/protocol/openid-connect/tokens
quarkus.oidc-client.token-path=/protocol/openid-connect/tokens

A more compact way to configure the token endpoint URL without the discovery is to set quarkus.oidc-client.token-path to an absolute URL:

quarkus.oidc-client.token-path=http://localhost:8180/auth/realms/quarkus/protocol/openid-connect/tokens

Setting quarkus.oidc-client.auth-server-url and quarkus.oidc-client.discovery-enabled is not required in this case.

1.1.2. Supported token grants

The main token grants that OidcClient can use to acquire the tokens are the client_credentials (default) and password grants.

1.1.2.1. Client credentials grant

Here is how OidcClient can be configured to use the client_credentials grant:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret

The client_credentials grant allows setting extra parameters for the token request by using quarkus.oidc-client.grant-options.client.<param-name>=<value>. Here is how to set the intended token recipient by using the audience parameter:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
# 'client' is a shortcut for `client_credentials`
quarkus.oidc-client.grant.type=client
quarkus.oidc-client.grant-options.client.audience=https://example.com/api

1.1.2.2. Password grant

Here is how OidcClient can be configured to use the password grant:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=password
quarkus.oidc-client.grant-options.password.username=alice
quarkus.oidc-client.grant-options.password.password=alice

It can be further customized by using a quarkus.oidc-client.grant-options.password configuration prefix, similar to how the client credentials grant can be customized.

1.1.2.3. Other grants

OidcClient can also help acquire the tokens by using grants that require some extra input parameters that cannot be captured in the configuration. These grants are refresh_token (with the external refresh token), authorization_code, and two grants which can be used to exchange the current access token, namely, urn:ietf:params:oauth:grant-type:token-exchange and urn:ietf:params:oauth:grant-type:jwt-bearer.

If you need to acquire an access token and have posted an existing refresh token to the current Quarkus endpoint, you must use the refresh_token grant. This grant employs an out-of-band refresh token to obtain a new token set. In this case, configure OidcClient as follows:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=refresh

Then you can use the OidcClient.refreshTokens method with a provided refresh token to get the access token.

Using the urn:ietf:params:oauth:grant-type:token-exchange or urn:ietf:params:oauth:grant-type:jwt-bearer grants might be required if you are building a complex microservices application and want to avoid the same Bearer token be propagated to and used by more than one service. See Token Propagation for Quarkus REST and Token Propagation for RESTEasy Classic for more details.

Using OidcClient to support the authorization code grant might be required if, for some reason, you cannot use the Quarkus OIDC extension to support Authorization Code Flow. If there is a very good reason for you to implement Authorization Code Flow, then you can configure OidcClient as follows:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=code

Then, you can use the OidcClient.accessTokens method to accept a Map of extra properties and pass the current code and redirect_uri parameters to exchange the authorization code for the tokens.

OidcClient also supports the urn:openid:params:grant-type:ciba grant:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=ciba

Then, you can use the OidcClient.accessTokens method to accept a Map of extra properties and pass the auth_req_id parameter to exchange the token authorization code.

1.1.2.4. Grant scopes

You might need to request that a specific set of scopes be associated with an issued access token. Use a dedicated quarkus.oidc-client.scopes list property, for example: quarkus.oidc-client.scopes=email,phone

1.1.3. Use OidcClient directly

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

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

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:

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.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


    @Inject
    @RestClient
    RestClientWithTokenHeaderParam restClient;

    @GET
    @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.

1.1.4. Inject tokens

You can inject Tokens that use OidcClient internally. Tokens can be used to acquire the access tokens and refresh them if necessary:

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

import io.quarkus.oidc.client.Tokens;

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

    @Inject Tokens tokens;

    @GET
    public String getResponse() {
        //  Get the access token, which might have been refreshed.
        String accessToken = tokens.getAccessToken();
        // Use the access token to configure MP RestClient Authorization header/etc
    }
}

1.1.5. Use OidcClients

io.quarkus.oidc.client.OidcClients is a container of OidcClients - it includes a default OidcClient and named clients which can be configured like this:

quarkus.oidc-client.client-enabled=false

quarkus.oidc-client.jwt-secret.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.jwt-secret.client-id=quarkus-app
quarkus.oidc-client.jwt-secret.credentials.jwt.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow

In this case, the default client is disabled with a client-enabled=false property. The jwt-secret client can be accessed like this:

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

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
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
    @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 OidcClient directly section.
注意

If you also use OIDC multitenancy, and each OIDC tenant has its own associated OidcClient, you can use a Vert.x RoutingContext tenant-id attribute. For example:

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

import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.OidcClients;
import io.vertx.ext.web.RoutingContext;

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

    @Inject
    OidcClients clients;
    @Inject
    RoutingContext context;

    @GET
    public String getResponse() {
        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
    }
}

You can also create a new OidcClient programmatically. For example, let’s assume you must create it at startup time:

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.runtime.OidcClientConfig;
import io.quarkus.oidc.client.runtime.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.inject.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 = OidcClientConfig
            .authServerUrl(oidcProviderAddress)
            .id("myclient")
            .clientId("backend-service")
            .credentials("secret")
            .grant(Type.PASSWORD)
            .grantOptions("password", Map.of("username", "alice", "password", "alice"))
            .build();
        return oidcClients.newClient(cfg);
    }
}

Now, you can use this client like this:

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

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

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

    @Inject
    OidcClientCreator oidcClientCreator;
    TokensHelper tokenHelper = new TokensHelper();

    @Inject
    @RestClient
    RestClientWithTokenHeaderParam restClient; 
1


    @GET
    @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 OidcClient directly section.

1.1.6. Inject named OidcClient and tokens

In case of multiple configured OidcClient objects, you can specify the OidcClient injection target by the extra qualifier @NamedOidcClient instead of working with OidcClients:

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 jakarta.ws.rs.Produces;
import io.smallrye.mutiny.Uni;
import io.quarkus.oidc.client.NamedOidcClient;
import io.quarkus.oidc.client.OidcClient;
import io.quarkus.oidc.client.runtime.TokensHelper;

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

    @Inject
    @NamedOidcClient("jwt-secret")
    OidcClient client;

    TokensHelper tokenHelper = new TokensHelper();

    @Inject
    @RestClient
    RestClientWithTokenHeaderParam restClient; 
1


    @GET
    @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 OidcClient directly section.

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

import java.io.IOException;

import jakarta.annotation.Priority;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientRequestFilter;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.ext.Provider;

import io.quarkus.oidc.client.NamedOidcClient;
import io.quarkus.oidc.client.Tokens;

@Provider
@Priority(Priorities.AUTHENTICATION)
@RequestScoped
public class OidcClientRequestCustomFilter implements ClientRequestFilter {

    @Inject
    @NamedOidcClient("jwt-secret")
    Tokens tokens;

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + tokens.getAccessToken());
    }
}

1.1.7. Use OidcClient in RestClient Reactive ClientFilter

Add the following Maven Dependency:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-rest-client-oidc-filter</artifactId>
</dependency>
注意

It will also bring io.quarkus:quarkus-oidc-client.

quarkus-rest-client-oidc-filter extension provides io.quarkus.oidc.client.filter.OidcClientRequestReactiveFilter.

It works similarly to the way OidcClientRequestFilter does (see Use OidcClient in MicroProfile RestClient client filter) - it uses OidcClient to acquire the access token, refresh it if needed, and set it as an HTTP Authorization Bearer scheme value. The difference is that it works with Reactive RestClient and implements a non-blocking client filter that does not block the current IO thread when acquiring or refreshing the tokens.

OidcClientRequestReactiveFilter delays an initial token acquisition until it is executed to avoid blocking an IO thread.

You can selectively register OidcClientRequestReactiveFilter by using either io.quarkus.oidc.client.reactive.filter.OidcClientFilter or org.eclipse.microprofile.rest.client.annotation.RegisterProvider annotations:

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientFilter;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@RegisterRestClient
@OidcClientFilter
@Path("/")
public interface ProtectedResourceService {

    @GET
    Uni<String> getUserName();
}

or

import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@RegisterRestClient
@RegisterProvider(OidcClientRequestReactiveFilter.class)
@Path("/")
public interface ProtectedResourceService {

    @GET
    Uni<String> getUserName();
}

OidcClientRequestReactiveFilter uses a default OidcClient by default. A named OidcClient can be selected with a quarkus.rest-client-oidc-filter.client-name configuration property. You can also select OidcClient by setting the value attribute of the @OidcClientFilter annotation. The client name set through annotation has higher priority than the quarkus.rest-client-oidc-filter.client-name configuration property. For example, given this jwt-secret named OIDC client declaration, you can refer to this client like this:

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientFilter;
import io.smallrye.mutiny.Uni;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@RegisterRestClient
@OidcClientFilter("jwt-secret")
@Path("/")
public interface ProtectedResourceService {

    @GET
    Uni<String> getUserName();
}

1.1.8. Use OidcClient in RestClient ClientFilter

Add the following Maven Dependency:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-client-oidc-filter</artifactId>
</dependency>
注意

It will also bring io.quarkus:quarkus-oidc-client.

quarkus-resteasy-client-oidc-filter extension provides io.quarkus.oidc.client.filter.OidcClientRequestFilter Jakarta REST ClientRequestFilter which uses OidcClient to acquire the access token, refresh it if needed, and set it as an HTTP Authorization Bearer scheme value.

By default, this filter will get OidcClient to acquire the first pair of access and refresh tokens at its initialization time. If the access tokens are short-lived and refresh tokens are unavailable, then the token acquisition should be delayed with quarkus.oidc-client.early-tokens-acquisition=false.

You can selectively register OidcClientRequestFilter by using either io.quarkus.oidc.client.filter.OidcClientFilter or org.eclipse.microprofile.rest.client.annotation.RegisterProvider annotations:

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientFilter;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@RegisterRestClient
@OidcClientFilter
@Path("/")
public interface ProtectedResourceService {

    @GET
    String getUserName();
}

or

import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientRequestFilter;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@RegisterRestClient
@RegisterProvider(OidcClientRequestFilter.class)
@Path("/")
public interface ProtectedResourceService {

    @GET
    String getUserName();
}

Alternatively, OidcClientRequestFilter can be registered automatically with all MP Rest or Jakarta REST clients if the quarkus.resteasy-client-oidc-filter.register-filter=true property is set.

OidcClientRequestFilter uses a default OidcClient by default. A named OidcClient can be selected with a quarkus.resteasy-client-oidc-filter.client-name configuration property. You can also select OidcClient by setting the value attribute of the @OidcClientFilter annotation. The client name set through annotation has higher priority than the quarkus.resteasy-client-oidc-filter.client-name configuration property. For example, given this jwt-secret named OIDC client declaration, you can refer to this client like this:

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import io.quarkus.oidc.client.filter.OidcClientFilter;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

@RegisterRestClient
@OidcClientFilter("jwt-secret")
@Path("/")
public interface ProtectedResourceService {

    @GET
    String getUserName();
}

1.1.9. Use a custom RestClient ClientFilter

If you prefer, you can use your own custom filter and inject Tokens:

import java.io.IOException;
import jakarta.annotation.Priority;
import jakarta.inject.Inject;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientRequestFilter;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.ext.Provider;
import io.quarkus.oidc.client.Tokens;

@Provider
@Priority(Priorities.AUTHENTICATION)
public class OidcClientRequestCustomFilter implements ClientRequestFilter {

    @Inject
    Tokens tokens;

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, "Bearer " + tokens.getAccessToken());
    }
}

The Tokens producer will acquire and refresh the tokens, and the custom filter will decide how and when to use the token.

You can also inject named Tokens, see Inject named OidcClient and Tokens

1.1.10. Refreshing access tokens

OidcClientRequestReactiveFilter, OidcClientRequestFilter and Tokens producers will refresh the current expired access token if the refresh token is available. Additionally, the quarkus.oidc-client.refresh-token-time-skew property can be used for a preemptive access token refreshment to avoid sending nearly expired access tokens that might cause HTTP 401 errors. For example, if this property is set to 3S and the access token will expire in less than 3 seconds, then this token will be auto-refreshed.

If the access token needs to be refreshed, but no refresh token is available, then an attempt is made to acquire a new token by using a configured grant, such as client_credentials.

Some OpenID Connect Providers will not return a refresh token in a client_credentials grant response. For example, starting from Keycloak 12, a refresh token will not be returned by default for client_credentials. The providers might also restrict the number of times a refresh token can be used.

1.1.11. Revoking access tokens

If your OpenId Connect provider, such as Keycloak, supports a token revocation endpoint, then OidcClient#revokeAccessToken can be used to revoke the current access token. The revocation endpoint URL will be discovered alongside the token request URI or can be configured with quarkus.oidc-client.revoke-path.

You might want to have the access token revoked if using this token with a REST client fails with an HTTP 401 status code or if the access token has already been used for a long time and you would like to refresh it.

This can be achieved by requesting a token refresh by using a refresh token. However, if the refresh token is unavailable, you can refresh it by revoking it first and then requesting a new access token.

1.1.12. OidcClient authentication

OidcClient has to authenticate to the OpenID Connect Provider for the client_credentials and other grant requests to succeed. All the OIDC Client Authentication options are supported, for example:

client_secret_basic:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.secret=mysecret

or

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.client-secret.value=mysecret

Or with the secret retrieved from a CredentialsProvider:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app

# This key is used to retrieve a secret from the map of credentials returned from CredentialsProvider
quarkus.oidc-client.credentials.client-secret.provider.key=mysecret-key
# This is the keyring provided to the CredentialsProvider when looking up the secret, set only if required by the CredentialsProvider implementation
quarkus.oidc.credentials.client-secret.provider.keyring-name=oidc
# Set it only if more than one CredentialsProvider can be registered
quarkus.oidc-client.credentials.client-secret.provider.name=oidc-credentials-provider

client_secret_post:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.client-secret.value=mysecret
quarkus.oidc-client.credentials.client-secret.method=post

client_secret_jwt, signature algorithm is HS256:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow

Or with the secret retrieved from a CredentialsProvider, signature algorithm is HS256:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app

# This is a key that will be used to retrieve a secret from the map of credentials returned from CredentialsProvider
quarkus.oidc-client.credentials.jwt.secret-provider.key=mysecret-key
# This is the keyring provided to the CredentialsProvider when looking up the secret, set only if required by the CredentialsProvider implementation
quarkus.oidc.credentials.client-secret.provider.keyring-name=oidc
# Set it only if more than one CredentialsProvider can be registered
quarkus.oidc-client.credentials.jwt.secret-provider.name=oidc-credentials-provider

private_key_jwt with the PEM key inlined in application.properties, and where the signature algorithm is RS256:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.key=Base64-encoded private key representation

private_key_jwt with the PEM key file, signature algorithm is RS256:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.key-file=privateKey.pem

private_key_jwt with the keystore file, signature algorithm is RS256:

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.key-store-file=keystore.pkcs12
quarkus.oidc-client.credentials.jwt.key-store-password=mypassword
quarkus.oidc-client.credentials.jwt.key-password=mykeypassword

# Private key alias inside the keystore
quarkus.oidc-client.credentials.jwt.key-id=mykeyAlias

Using client_secret_jwt or private_key_jwt authentication methods ensures that no client secret goes over the wire.

1.1.12.1. Additional JWT authentication options

If either client_secret_jwt or private_key_jwt authentication methods are used, then the JWT signature algorithm, key identifier, audience, subject, and issuer can be customized, for example:

# private_key_jwt client authentication

quarkus.oidc-client.auth-server-url=http://localhost:8180/auth/realms/quarkus/
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.key-file=privateKey.pem

# This is a token key identifier 'kid' header - set it if your OpenID Connect provider requires it.
# Note that if the key is represented in a JSON Web Key (JWK) format with a `kid` property, then
# using 'quarkus.oidc-client.credentials.jwt.token-key-id' is unnecessary.
quarkus.oidc-client.credentials.jwt.token-key-id=mykey

# Use the RS512 signature algorithm instead of the default RS256
quarkus.oidc-client.credentials.jwt.signature-algorithm=RS512

# The token endpoint URL is the default audience value; use the base address URL instead:
quarkus.oidc-client.credentials.jwt.audience=${quarkus.oidc-client.auth-server-url}

# custom subject instead of the client ID:
quarkus.oidc-client.credentials.jwt.subject=custom-subject

# custom issuer instead of the client ID:
quarkus.oidc-client.credentials.jwt.issuer=custom-issuer

1.1.12.2. JWT Bearer

RFC7523 explains how JWT Bearer tokens can be used to authenticate clients, see the Using JWTs for Client Authentication section for more information.

It can be enabled as follows:

quarkus.oidc-client.auth-server-url=${auth-server-url}
quarkus.oidc-client.client-id=quarkus-app
quarkus.oidc-client.credentials.jwt.source=bearer

Next, the JWT bearer token must be provided as a client_assertion parameter to the OIDC client.

Quarkus can load the JWT bearer token from a file system. For example, in Kubernetes, a service account token projection can be mounted to a /var/run/secrets/tokens path. Then all you need to do is configure a JWT bearer token path as follows:

quarkus.oidc-client.credentials.jwt.token-path=/var/run/secrets/tokens 
1
1
Path to a JWT bearer token. Quarkus loads a new token from a filesystem and reload it when the token has expired.

Your other option is to use OidcClient methods for acquiring or refreshing tokens which accept additional grant parameters, for example, oidcClient.getTokens(Map.of("client_assertion", "ey…​")).

If you work work with the OIDC client filters then you must register a custom filter which will provide this assertion.

Here is an example of the Quarkus REST (formerly RESTEasy Reactive) custom filter:

package io.quarkus.it.keycloak;

import java.util.Map;

import io.quarkus.oidc.client.reactive.filter.runtime.AbstractOidcClientRequestReactiveFilter;
import io.quarkus.oidc.common.runtime.OidcConstants;
import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;

@Priority(Priorities.AUTHENTICATION)
public class OidcClientRequestCustomFilter extends AbstractOidcClientRequestReactiveFilter {

    @Override
    protected Map<String, String> additionalParameters() {
        return Map.of(OidcConstants.CLIENT_ASSERTION, "ey...");
    }
}

Here is an example of the RESTEasy Classic custom filter:

package io.quarkus.it.keycloak;

import java.util.Map;

import io.quarkus.oidc.client.filter.runtime.AbstractOidcClientRequestFilter;
import io.quarkus.oidc.common.runtime.OidcConstants;
import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;

@Priority(Priorities.AUTHENTICATION)
public class OidcClientRequestCustomFilter extends AbstractOidcClientRequestFilter {

    @Override
    protected Map<String, String> additionalParameters() {
        return Map.of(OidcConstants.CLIENT_ASSERTION, "ey...");
    }
}

1.1.12.3. Apple POST JWT

Apple OpenID Connect Provider uses a client_secret_post method where a secret is a JWT produced with a private_key_jwt authentication method but with Apple account-specific issuer and subject properties.

quarkus-oidc-client supports a non-standard client_secret_post_jwt authentication method, which can be configured as follows:

quarkus.oidc-client.auth-server-url=${apple.url}
quarkus.oidc-client.client-id=${apple.client-id}
quarkus.oidc-client.credentials.client-secret.method=post-jwt

quarkus.oidc-client.credentials.jwt.key-file=ecPrivateKey.pem
quarkus.oidc-client.credentials.jwt.signature-algorithm=ES256
quarkus.oidc-client.credentials.jwt.subject=${apple.subject}
quarkus.oidc-client.credentials.jwt.issuer=${apple.issuer}

1.1.12.4. Mutual TLS

Some OpenID Connect Providers require that a client is authenticated as part of the mutual TLS (mTLS) authentication process.

quarkus-oidc-client can be configured as follows to support mTLS:

quarkus.oidc-client.tls.tls-configuration-name=oidc-client

# configure hostname verification if necessary
#quarkus.tls.oidc-client.hostname-verification-algorithm=NONE

# Keystore configuration
quarkus.tls.oidc-client.key-store.p12.path=client-keystore.p12
quarkus.tls.oidc-client.key-store.p12.password=${key-store-password}

# Add more keystore properties if needed:
#quarkus.tls.oidc-client.key-store.p12.alias=keyAlias
#quarkus.tls.oidc-client.key-store.p12.alias-password=keyAliasPassword

# Truststore configuration
quarkus.tls.oidc-client.trust-store.p12.path=client-truststore.p12
quarkus.tls.oidc-client.trust-store.p12.password=${trust-store-password}
# Add more truststore properties if needed:
#quarkus.tls.oidc-client.trust-store.p12.alias=certAlias

1.1.13. OIDC Client SPI

When your custom extension must acquire OIDC tokens using one of the OIDC token grants supported by OIDC client, this extension can depend on the OIDC Client SPI only and let OIDC client itself acquire and refresh access tokens as necessary.

Add the following dependency:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-oidc-client-spi</artifactId>
</dependency>

Next update your extension to use io.quarkus.oidc.client.spi.TokenProvider CDI bean as required, for example:

package org.acme.extension;

import jakarta.inject.Inject;
import io.quarkus.oidc.client.spi.TokenProvider;

public class ExtensionOAuth2Support {

   @Inject
   TokenProvider tokenProvider;

   public Uni<String> getAccessToken() {
       return tokenProvider.getAccessToken();
   }
}

Currently, io.quarkus.oidc.client.spi.TokenProvider is only available for default OIDC clients, since custom extensions are unlikely to be aware of multiple named OIDC clients.

1.1.14. Testing

Start by adding the following dependencies to your test project:

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-junit5</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.awaitility</groupId>
    <artifactId>awaitility</artifactId>
    <scope>test</scope>
</dependency>

1.1.14.1. Wiremock

Add the following dependencies to your test project:

<dependency>
    <groupId>org.wiremock</groupId>
    <artifactId>wiremock</artifactId>
    <scope>test</scope>
    <version>${wiremock.version}</version> 
1

</dependency>
1
Use a proper Wiremock version. All available versions can be found here.

Write a Wiremock-based QuarkusTestResourceLifecycleManager, for example:

package io.quarkus.it.keycloak;

import static com.github.tomakehurst.wiremock.client.WireMock.matching;
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;

import java.util.HashMap;
import java.util.Map;

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.core.Options.ChunkedEncodingPolicy;

import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;

public class KeycloakRealmResourceManager implements QuarkusTestResourceLifecycleManager {
    private WireMockServer server;

    @Override
    public Map<String, String> start() {

        server = new WireMockServer(wireMockConfig().dynamicPort().useChunkedTransferEncoding(ChunkedEncodingPolicy.NEVER));
        server.start();

        server.stubFor(WireMock.post("/tokens")
                .withRequestBody(matching("grant_type=password&username=alice&password=alice"))
                .willReturn(WireMock
                        .aResponse()
                        .withHeader("Content-Type", "application/json")
                        .withBody(
                                "{\"access_token\":\"access_token_1\", \"expires_in\":4, \"refresh_token\":\"refresh_token_1\"}")));
        server.stubFor(WireMock.post("/tokens")
                .withRequestBody(matching("grant_type=refresh_token&refresh_token=refresh_token_1"))
                .willReturn(WireMock
                        .aResponse()
                        .withHeader("Content-Type", "application/json")
                        .withBody(
                                "{\"access_token\":\"access_token_2\", \"expires_in\":4, \"refresh_token\":\"refresh_token_1\"}")));


        Map<String, String> conf = new HashMap<>();
        conf.put("keycloak.url", server.baseUrl());
        return conf;
    }

    @Override
    public synchronized void stop() {
        if (server != null) {
            server.stop();
            server = null;
        }
    }
}

Prepare the REST test endpoints. You can have the test front-end endpoint, which uses the injected MP REST client with a registered OidcClient filter, call the downstream endpoint. This endpoint echoes the token back. For example, see the integration-tests/oidc-client-wiremock in the main Quarkus repository.

Set application.properties, for example:

# Use the 'keycloak.url' property set by the test KeycloakRealmResourceManager
quarkus.oidc-client.auth-server-url=${keycloak.url:replaced-by-test-resource}
quarkus.oidc-client.discovery-enabled=false
quarkus.oidc-client.token-path=/tokens
quarkus.oidc-client.client-id=quarkus-service-app
quarkus.oidc-client.credentials.secret=secret
quarkus.oidc-client.grant.type=password
quarkus.oidc-client.grant-options.password.username=alice
quarkus.oidc-client.grant-options.password.password=alice

And finally, write the test code. Given the Wiremock-based resource above, the first test invocation should return the access_token_1 access token, which will expire in 4 seconds. Use awaitility to wait for about 5 seconds, and now the next test invocation should return the access_token_2 access token, which confirms the expired access_token_1 access token has been refreshed.

1.1.14.2. Keycloak

If you work with Keycloak, you can use the same approach described in the OpenID Connect Bearer Token Integration testing Keycloak section.

1.1.15. How to check the errors in the logs

Enable io.quarkus.oidc.client.runtime.OidcClientImpl TRACE level logging to see more details about the token acquisition and refresh errors:

quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientImpl".level=TRACE
quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientImpl".min-level=TRACE

Enable io.quarkus.oidc.client.runtime.OidcClientRecorder TRACE level logging to see more details about the OidcClient initialization errors:

quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientRecorder".level=TRACE
quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientRecorder".min-level=TRACE
Red Hat logoGithubredditYoutubeTwitter

学习

尝试、购买和销售

社区

關於紅帽

我们提供强化的解决方案,使企业能够更轻松地跨平台和环境(从核心数据中心到网络边缘)工作。

让开源更具包容性

红帽致力于替换我们的代码、文档和 Web 属性中存在问题的语言。欲了解更多详情,请参阅红帽博客.

关于红帽文档

Legal Notice

Theme

© 2026 Red Hat
返回顶部