From 08fbdf8606c2075d1cdc2c6f60d57cc6c1a6bf9f Mon Sep 17 00:00:00 2001 From: Rolfe Dlugy-Hegwer Date: Fri, 8 Dec 2023 10:00:28 -0500 Subject: [PATCH] Edit the OIDC and OAuth2 Client and Filters Reference Guide --- ...urity-openid-connect-client-reference.adoc | 79 ++++++++++--------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc index 844683161d543..dd6c988ccc53a 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc @@ -3,20 +3,22 @@ This guide is maintained in the main Quarkus repository and pull requests should be submitted there: https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc //// -= OpenID Connect (OIDC) and OAuth2 Client and Filters Reference Guide += OpenID Connect (OIDC) and OAuth2 client and filters include::_attributes.adoc[] +:diataxis-type: reference :categories: security :topics: security,oidc,client :extensions: io.quarkus:quarkus-oidc-client -This reference guide explains how to use: +You can use Quarkus extensions to acquire and refresh access tokens from OIDC and OAuth 2.0 compliant servers and propagate access tokens. - - `quarkus-oidc-client`, `quarkus-oidc-client-reactive-filter` and `quarkus-oidc-client-filter` extensions to acquire and refresh access tokens from OpenID Connect and OAuth 2.0 compliant Authorization Servers such as link:https://www.keycloak.org[Keycloak] - - `quarkus-oidc-token-propagation-reactive` and `quarkus-oidc-token-propagation` extensions to propagate the current `Bearer` or `Authorization Code Flow` access tokens +Here, you can learn how to use `quarkus-oidc-client`, `quarkus-oidc-client-reactive-filter` and `quarkus-oidc-client-filter` extensions to acquire and refresh access tokens from OpenID Connect and OAuth 2.0 compliant servers such as link:https://www.keycloak.org[Keycloak]. + +You can also learn how to use `quarkus-oidc-token-propagation-reactive` and `quarkus-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 xref:security-openid-connect-client.adoc[OpenID Connect Client and Token Propagation Quickstart]. +Also see xref:security-openid-connect-client.adoc[OpenID Connect client and token propagation quickstart]. == OidcClient @@ -30,11 +32,11 @@ Add the following dependency: ---- -`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`. +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 the build time with the IDP token endpoint URL, which can be auto-discovered or manually configured, and uses this endpoint to acquire access tokens using the token grants such as `client_credentials` or `password` and refresh the tokens using a `refresh_token` grant. +`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. -=== Token Endpoint Configuration +=== 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`. @@ -47,7 +49,7 @@ 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 not available 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: +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: [source, properties] ---- @@ -66,11 +68,11 @@ quarkus.oidc-client.token-path=http://localhost:8180/auth/realms/quarkus/protoco Setting `quarkus.oidc-client.auth-server-url` and `quarkus.oidc-client.discovery-enabled` is not required in this case. -=== Supported Token Grants +=== Supported token grants The main token grants that `OidcClient` can use to acquire the tokens are the `client_credentials` (default) and `password` grants. -==== Client Credentials Grant +==== Client credentials grant Here is how `OidcClient` can be configured to use the `client_credentials` grant: @@ -93,7 +95,7 @@ quarkus.oidc-client.grant.type=client quarkus.oidc-client.grant-options.client.audience=https://example.com/api ---- -==== Password Grant +==== Password grant Here is how `OidcClient` can be configured to use the `password` grant: @@ -107,13 +109,14 @@ quarkus.oidc-client.grant-options.password.username=alice quarkus.oidc-client.grant-options.password.password=alice ---- -It can be further customized using a `quarkus.oidc-client.grant-options.password` configuration prefix, similar to how the client credentials grant can be customized. +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. -==== Other Grants +==== 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`, as well as two grants which can be used to exchange the current access token, `urn:ietf:params:oauth:grant-type:token-exchange` and `urn:ietf:params:oauth:grant-type:jwt-bearer`. +`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`. -Using the `refresh_token` grant, which uses an out-of-band refresh token to acquire a new set of tokens, will be required if the existing refresh token has been posted to the current Quarkus endpoint for it to acquire the access token. In this case, `OidcClient` needs to be configured as follows: +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: [source,properties] ---- @@ -149,7 +152,7 @@ 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 authorization code for the tokens. +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. ==== Grant scopes @@ -196,7 +199,7 @@ public class OidcClientResource { } ---- -=== Inject Tokens +=== Inject tokens You can inject `Tokens` that use `OidcClient` internally. `Tokens` can be used to acquire the access tokens and refresh them if necessary: @@ -236,7 +239,7 @@ quarkus.oidc-client.jwt-secret.client-id=quarkus-app quarkus.oidc-client.jwt-secret.credentials.jwt.secret=AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow ---- -Note that in this case, the default client is disabled with a `client-enabled=false` property. The `jwt-secret` client can be accessed like this: +In this case, the default client is disabled with a `client-enabled=false` property. The `jwt-secret` client can be accessed like this: [source,java] ---- @@ -328,9 +331,9 @@ public class OidcClientResource { ---- [[named-oidc-clients]] -=== Inject named OidcClient and Tokens +=== Inject named OidcClient and tokens -In case of multiple configured ``OidcClient``s you can specify the `OidcClient` injection target by the extra qualifier `@NamedOidcClient` instead of working with `OidcClients`: +In case of multiple configured `OidcClient` objects, you can specify the `OidcClient` injection target by the extra qualifier `@NamedOidcClient` instead of working with `OidcClients`: [source,java] ---- @@ -529,7 +532,7 @@ public interface ProtectedResourceService { } ---- -=== Use Custom RestClient ClientFilter +=== Use a custom RestClient ClientFilter If you prefer, you can use your own custom filter and inject `Tokens`: @@ -556,26 +559,26 @@ The `Tokens` producer will acquire and refresh the tokens, and the custom filter You can also inject named `Tokens`, see <> [[refresh-access-tokens]] -=== Refreshing Access Tokens +=== 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 using a configured grant, such as `client_credentials`. +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`. -Note that 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. +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. [[revoke-access-tokens]] -=== Revoking Access Tokens +=== 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 HTTP `401` or if the access token has already been used for a long time and you'd like to refresh it. +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 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. +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. [[oidc-client-authentication]] -=== OidcClient Authentication +=== OidcClient authentication `OidcClient` has to authenticate to the OpenID Connect Provider for the `client_credentials` and other grant requests to succeed. All the https://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication[OIDC Client Authentication] options are supported, for example: @@ -668,7 +671,7 @@ 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. -==== Additional JWT Authentication options +==== 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: @@ -912,7 +915,7 @@ public class OidcRequestCustomizer implements OidcRequestFilter { [[token-propagation-reactive]] == Token Propagation Reactive -The `quarkus-oidc-token-propagation-reactive` extension provides RestEasy Reactive Client `io.quarkus.oidc.token.propagation.reactive.AccessTokenRequestReactiveFilter` that simplifies the propagation of authentication information by propagating the xref:security-oidc-bearer-token-authentication.adoc[Bearer token] present in the current active request or the token acquired from the xref:security-oidc-code-flow-authentication.adoc[Authorization code flow mechanism], as the HTTP `Authorization` header's `Bearer` scheme value. +The `quarkus-oidc-token-propagation-reactive` extension provides a RestEasy Reactive Client, `io.quarkus.oidc.token.propagation.reactive.AccessTokenRequestReactiveFilter`, that simplifies the propagation of authentication information. This client propagates the xref:security-oidc-bearer-token-authentication.adoc[bearer token] present in the currently active request or the token acquired from the xref:security-oidc-code-flow-authentication.adoc[authorization code flow mechanism] as the HTTP `Authorization` header's `Bearer` scheme value. You can selectively register `AccessTokenRequestReactiveFilter` by using either `io.quarkus.oidc.token.propagation.AccessToken` or `org.eclipse.microprofile.rest.client.annotation.RegisterProvider` annotation, for example: @@ -952,7 +955,7 @@ public interface ProtectedResourceService { Additionally, `AccessTokenRequestReactiveFilter` can support a complex application that needs to exchange the tokens before propagating them. -If you work with link:https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange[Keycloak] or other OpenID Connect Providers which support a link:https://tools.ietf.org/html/rfc8693[Token Exchange] token grant then you can configure `AccessTokenRequestReactiveFilter` to exchange the token like this: +If you work with link:https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange[Keycloak] or another OIDC provider that supports a link:https://tools.ietf.org/html/rfc8693[Token Exchange] token grant, then you can configure `AccessTokenRequestReactiveFilter` to exchange the token like this: [source,properties] ---- @@ -993,7 +996,7 @@ The `io.quarkus.oidc.token.propagation.JsonWebTokenRequestFilter` provides the s When you need to propagate the current Authorization Code Flow access token, then the immediate token propagation will work well - as the code flow access tokens (as opposed to ID tokens) are meant to be propagated for the current Quarkus endpoint to access the remote services on behalf of the currently authenticated user. -However, the direct end-to-end Bearer token propagation should be avoided if possible. For example, `Client -> Service A -> Service B` where `Service B` receives a token sent by `Client` to `Service A`. In such cases, `Service B` cannot distinguish if the token came from `Service A` or from `Client` directly. For `Service B` to verify the token came from `Service A`, it should be able to assert a new issuer and audience claims. +However, the direct end-to-end Bearer token propagation should be avoided. For example, `Client -> Service A -> Service B` where `Service B` receives a token sent by `Client` to `Service A`. In such cases, `Service B` cannot distinguish if the token came from `Service A` or from `Client` directly. For `Service B` to verify the token came from `Service A`, it should be able to assert a new issuer and audience claims. Additionally, a complex application might need to exchange or update the tokens before propagating them. For example, the access context might be different when `Service A` is accessing `Service B`. In this case, `Service A` might be granted a narrow or completely different set of scopes to access `Service B`. @@ -1039,7 +1042,7 @@ public interface ProtectedResourceService { Alternatively, `AccessTokenRequestFilter` can be registered automatically with all MP Rest or Jakarta REST clients if the `quarkus.oidc-token-propagation.register-filter` property is set to `true` and `quarkus.oidc-token-propagation.json-web-token` property is set to `false` (which is a default value). -==== Exchange Token Before Propagation +==== Exchange token before propagation If the current access token needs to be exchanged before propagation and you work with link:https://www.keycloak.org/docs/latest/securing_apps/#_token-exchange[Keycloak] or other OpenID Connect Provider which supports a link:https://tools.ietf.org/html/rfc8693[Token Exchange] token grant then you can configure `AccessTokenRequestFilter` like this: @@ -1077,7 +1080,7 @@ Note `AccessTokenRequestFilter` will use `OidcClient` to exchange the current to Using `JsonWebTokenRequestFilter` is recommended if you work with Bearer JWT tokens where these tokens can have their claims, such as `issuer` and `audience` modified and the updated tokens secured (for example, re-signed) again. It expects an injected `org.eclipse.microprofile.jwt.JsonWebToken` and, therefore, will not work with the opaque tokens. Also, if your OpenID Connect Provider supports a Token Exchange protocol, then it is recommended to use `AccessTokenRequestFilter` instead - as both JWT and opaque bearer tokens can be securely exchanged with `AccessTokenRequestFilter`. -`JsonWebTokenRequestFilter` makes it easy for `Service A` implementations to update the injected `org.eclipse.microprofile.jwt.JsonWebToken` with the new `issuer` and `audience` claim values and secure the updated token again with a new signature. The only difficult step is to ensure that `Service A` has a signing key; it should be provisioned from a secure file system or remote secure storage such as Vault. +`JsonWebTokenRequestFilter` makes it easy for `Service A` implementations to update the injected `org.eclipse.microprofile.jwt.JsonWebToken` with the new `issuer` and `audience` claim values and secure the updated token again with a new signature. The only difficult step is ensuring that `Service A` has a signing key; it should be provisioned from a secure file system or remote secure storage such as Vault. You can selectively register `JsonWebTokenRequestFilter` by using either `io.quarkus.oidc.token.propagation.JsonWebToken` or `org.eclipse.microprofile.rest.client.annotation.RegisterProvider`, for example: @@ -1115,7 +1118,7 @@ public interface ProtectedResourceService { Alternatively, `JsonWebTokenRequestFilter` can be registered automatically with all MicroProfile REST or Jakarta REST clients if both `quarkus.oidc-token-propagation.register-filter` and `quarkus.oidc-token-propagation.json-web-token` properties are set to `true`. -==== Update Token Before Propagation +==== Update token before propagation If the injected token needs to have its `iss` (issuer) or `aud` (audience) claims updated and secured again with a new signature, then you can configure `JsonWebTokenRequestFilter` like this: @@ -1131,7 +1134,7 @@ smallrye.jwt.new-token.audience=http://downstream-resource smallrye.jwt.new-token.override-matching-claims=true ---- -As already noted above, use `AccessTokenRequestFilter` if you work with Keycloak or OpenID Connect Provider, which supports a Token Exchange protocol. +As noted above, use `AccessTokenRequestFilter` if you work with Keycloak or OpenID Connect Provider, which supports a Token Exchange protocol. [[integration-testing-token-propagation]] === Testing @@ -1215,7 +1218,7 @@ VertxDynamicGraphQLClient client = builder.build(); == References -* xref:security-openid-connect-client.adoc[OpenID Connect Client and Token Propagation Quickstart] +* xref:security-openid-connect-client.adoc[OpenID Connect client and token propagation quickstart]. * xref:security-oidc-bearer-token-authentication.adoc[OIDC Bearer token authentication] * xref:security-oidc-code-flow-authentication.adoc[OIDC code flow mechanism for protecting web applications] * xref:security-overview.adoc[Quarkus Security overview]