From 88886257ffdb19dfbdbbf7727a848fbffa8726b1 Mon Sep 17 00:00:00 2001 From: Michelle Purcell Date: Tue, 12 Sep 2023 23:16:33 +0100 Subject: [PATCH] Doc style and link enhancements fix typo Fixes Minor fixes fix fix remove html file Peer review fixes Fixes to section headings level 3 fix minortweak Update docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc Co-authored-by: Sergey Beryozkin Update docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc fix --- ...rity-oidc-bearer-token-authentication.adoc | 435 +++++++++++------- 1 file changed, 257 insertions(+), 178 deletions(-) diff --git a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc index b6d397bbb3d72..4e7cee5dc1410 100644 --- a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc +++ b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc @@ -17,43 +17,45 @@ Secure HTTP access to Jakarta REST (formerly known as JAX-RS) endpoints in your Quarkus supports the Bearer token authentication mechanism through the Quarkus OpenID Connect (OIDC) extension. -The bearer tokens are issued by OIDC and OAuth 2.0 compliant authorization servers, such as link:https://www.keycloak.org[Keycloak]. +The bearer tokens are issued by OIDC and OAuth 2.0-compliant authorization servers, such as link:https://www.keycloak.org[Keycloak]. Bearer token authentication is the process of authorizing HTTP requests based on the existence and validity of a bearer token. The bearer token provides information about the subject of the call, which is used to determine whether or not an HTTP resource can be accessed. The following diagrams outline the Bearer token authentication mechanism in Quarkus: -.Bearer token authentication mechanism in Quarkus with Single-page application +.Bearer token authentication mechanism in Quarkus with single-page application image::security-bearer-token-authorization-mechanism-1.png[alt=Bearer token authentication, width="60%", align=center] -1. The Quarkus service retrieves verification keys from the OpenID Connect provider. The verification keys are used to verify the bearer access token signatures. -2. The Quarkus user accesses the Single-page application. -3. The Single-page application uses Authorization Code Flow to authenticate the user and retrieve tokens from the OpenID Connect provider. -4. The Single-page application uses the access token to retrieve the service data from the Quarkus service. -5. The Quarkus service verifies the bearer access token signature using the verification keys, checks the token expiry date and other claims, allows the request to proceed if the token is valid, and returns the service response to the Single-page application. -6. The Single-page application returns the same data to the Quarkus user. +1. The Quarkus service retrieves verification keys from the OIDC provider. +The verification keys are used to verify the bearer access token signatures. +2. The Quarkus user accesses the single-page application (SPA). +3. The single-page application uses Authorization Code Flow to authenticate the user and retrieve tokens from the OIDC provider. +4. The single-page application uses the access token to retrieve the service data from the Quarkus service. +5. The Quarkus service verifies the bearer access token signature by using the verification keys, checks the token expiry date and other claims, allows the request to proceed if the token is valid, and returns the service response to the single-page application. +6. The single-page application returns the same data to the Quarkus user. .Bearer token authentication mechanism in Quarkus with Java or command line client image::security-bearer-token-authorization-mechanism-2.png[alt=Bearer token authentication, width="60%", align=center] -1. The Quarkus service retrieves verification keys from the OpenID Connect provider. The verification keys are used to verify the bearer access token signatures. -2. The Client uses `client_credentials` that requires client ID and secret or password grant, which also requires client ID, secret, user name, and password to retrieve the access token from the OpenID Connect provider. -3. The Client uses the access token to retrieve the service data from the Quarkus service. -4. The Quarkus service verifies the bearer access token signature using the verification keys, checks the token expiry date and other claims, allows the request to proceed if the token is valid, and returns the service response to the Client. +1. The Quarkus service retrieves verification keys from the OIDC provider. +The verification keys are used to verify the bearer access token signatures. +2. The client uses `client_credentials` that requires client ID and secret or password grant, which requires client ID, secret, username, and password to retrieve the access token from the OIDC provider. +3. The client uses the access token to retrieve the service data from the Quarkus service. +4. The Quarkus service verifies the bearer access token signature by using the verification keys, checks the token expiry date and other claims, allows the request to proceed if the token is valid, and returns the service response to the client. -If you need to authenticate and authorize the users using OpenID Connect Authorization Code Flow, see xref:security-oidc-code-flow-authentication.adoc[OIDC code flow mechanism for protecting web applications]. -Also, if you use Keycloak and bearer tokens, see xref:security-keycloak-authorization.adoc[Using Keycloak to Centralize Authorization]. +If you need to authenticate and authorize users by using OIDC authorization code flow, see the Quarkus xref:security-oidc-code-flow-authentication.adoc[OpenID Connect authorization code flow mechanism for protecting web applications] guide. +Also, if you use Keycloak and bearer tokens, see the Quarkus xref:security-keycloak-authorization.adoc[Using Keycloak to Centralize Authorization] guide. -To learn about how you can protect service applications by using OIDC Bearer token authentication, see xref:security-oidc-bearer-token-authentication-tutorial.adoc[OIDC Bearer token authentication tutorial]. +To learn about how you can protect service applications by using OIDC Bearer token authentication, see the following tutorial: -If you want to protect web applications by using OIDC authorization code flow authentication, see xref:security-oidc-code-flow-authentication-concept.adoc[OIDC authorization code flow authentication]. +* xref:security-oidc-bearer-token-authentication-tutorial.adoc[Protect a web application by using OpenID Connect (OIDC) authorization code flow]. -For information about how to support multiple tenants, see xref:security-openid-connect-multitenancy.adoc[Using OpenID Connect Multi-Tenancy]. +For information about how to support multiple tenants, see the Quarkus xref:security-openid-connect-multitenancy.adoc[Using OpenID Connect Multi-Tenancy] guide. === Accessing JWT claims -If you need to access JWT token claims then you can inject `JsonWebToken`: +If you need to access JWT token claims, you can inject `JsonWebToken`: [source,java] ---- @@ -82,41 +84,44 @@ public class AdminResource { } ---- -Injection of `JsonWebToken` is supported in `@ApplicationScoped`, `@Singleton` and `@RequestScoped` scopes however the use of `@RequestScoped` is required if the individual claims are injected as simple types, please see xref:security-jwt.adoc#supported-injection-scopes[Support Injection Scopes for JsonWebToken and Claims] for more details. +Injection of `JsonWebToken` is supported in `@ApplicationScoped`, `@Singleton`, and `@RequestScoped` scopes. +However, the use of `@RequestScoped` is required if the individual claims are injected as simple types. +For more information, see the xref:security-jwt.adoc#supported-injection-scopes[Supported injection scopes] section of the Quarkus "Using JWT RBAC" guide. [[user-info]] -=== User Info +=== `UserInfo` JSON objects -Set `quarkus.oidc.authentication.user-info-required=true` if a UserInfo JSON object from the OIDC userinfo endpoint has to be requested. -A request will be sent to the OpenID Provider UserInfo endpoint and an `io.quarkus.oidc.UserInfo` (a simple `jakarta.json.JsonObject` wrapper) object will be created. -`io.quarkus.oidc.UserInfo` can be either injected or accessed as a SecurityIdentity `userinfo` attribute. +If you must request a UserInfo JSON object from the OIDC `UserInfo` endpoint, set `quarkus.oidc.authentication.user-info-required=true`. +A request is sent to the OIDC provider `UserInfo` endpoint, and an `io.quarkus.oidc.UserInfo` (a simple `javax.json.JsonObject` wrapper) object is created. +`io.quarkus.oidc.UserInfo` can be injected or accessed as a `SecurityIdentity` `userinfo` attribute. [[config-metadata]] -=== Configuration Metadata +=== Configuration metadata The current tenant's discovered link:https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata[OpenID Connect Configuration Metadata] is represented by `io.quarkus.oidc.OidcConfigurationMetadata` and can be either injected or accessed as a `SecurityIdentity` `configuration-metadata` attribute. The default tenant's `OidcConfigurationMetadata` is injected if the endpoint is public. [[token-claims-and-security-identity-roles]] -=== Token Claims And SecurityIdentity Roles +=== Token claims and `SecurityIdentity` roles -SecurityIdentity roles can be mapped from the verified JWT access tokens as follows: +You can map `SecurityIdentity` roles from the verified JWT access tokens as follows: -* If `quarkus.oidc.roles.role-claim-path` property is set and matching array or string claims are found then the roles are extracted from these claims. - For example, `customroles`, `customroles/array`, `scope`, `"http://namespace-qualified-custom-claim"/roles`, `"http://namespace-qualified-roles"`, etc. -* If `groups` claim is available then its value is used -* If `realm_access/roles` or `resource_access/client_id/roles` (where `client_id` is the value of the `quarkus.oidc.client-id` property) claim is available then its value is used. - This check supports the tokens issued by Keycloak +* If the `quarkus.oidc.roles.role-claim-path` property is set, and matching array or string claims are found, then the roles are extracted from these claims. +For example, `customroles`, `customroles/array`, `scope`, `"http://namespace-qualified-custom-claim"/roles`, `"http://namespace-qualified-roles"`. +* If a group's claim is available, then its value is used. +* If a `realm_access/roles` or `resource_access/client_id/roles` (where `client_id` is the value of the `quarkus.oidc.client-id` property) claim is available, then its value is used. +This check supports the tokens issued by Keycloak. -If the token is opaque (binary) then a `scope` property from the remote token introspection response will be used. +If the token is opaque (binary), then a `scope` property from the remote token introspection response is used. -If UserInfo is the source of the roles then set `quarkus.oidc.authentication.user-info-required=true` and `quarkus.oidc.roles.source=userinfo`, and if needed, `quarkus.oidc.roles.role-claim-path`. +If `UserInfo` is the source of the roles, then set `quarkus.oidc.authentication.user-info-required=true` and `quarkus.oidc.roles.source=userinfo`, and if needed, set `quarkus.oidc.roles.role-claim-path`. -Additionally, a custom `SecurityIdentityAugmentor` can also be used to add the roles as documented in xref:security-customization.adoc#security-identity-customization[Security Identity Customization]. +A custom `SecurityIdentityAugmentor` can also be used to add the roles. +For more information, see the xref:security-customization.adoc#security-identity-customization[Security identity customization] section of the Quarkus "Security tips and tricks" guide. [[token-scopes-and-security-identity-permissions]] -=== Token scopes And SecurityIdentity permissions +=== Token scopes and `SecurityIdentity` permissions SecurityIdentity permissions are mapped in the form of the `io.quarkus.security.StringPermission` from the scope parameter of the <>, using the same claim separator. @@ -153,22 +158,25 @@ public class ProtectedResource { } ---- + <1> Only requests with OpenID Connect scope `email` are going to be granted access. <2> The read access is limited to the client requests with scope `orders_read`. -Please refer to the Permission annotation section of the xref:security-authorize-web-endpoints-reference.adoc#permission-annotation[Authorization of web endpoints] -guide for more information about the `io.quarkus.security.PermissionsAllowed` annotation. +For more information about the `io.quarkus.security.PermissionsAllowed` annotation, see the xref:security-authorize-web-endpoints-reference.adoc#permission-annotation[Permission annotation] section of the "Authorization of web endpoints" guide. [[token-verification-introspection]] -=== Token Verification And Introspection +=== Token verification and introspection -If the token is a JWT token then, by default, it will be verified with a `JsonWebKey` (JWK) key from a local `JsonWebKeySet` retrieved from the OpenID Connect Provider's JWK endpoint. The token's key identifier `kid` header value will be used to find the matching JWK key. -If no matching `JWK` is available locally then `JsonWebKeySet` will be refreshed by fetching the current key set from the JWK endpoint. The `JsonWebKeySet` refresh can be repeated only after the `quarkus.oidc.token.forced-jwk-refresh-interval` (default is 10 minutes) expires. -If no matching `JWK` is available after the refresh then the JWT token will be sent to the OpenID Connect Provider's token introspection endpoint. +If the token is a JWT token, then, by default, it is verified with a `JsonWebKey` (JWK) key from a local `JsonWebKeySet`, retrieved from the OIDC provider's JWK endpoint. +The key identifier (`kid`) header value is used to find the matching JWK key. +If no matching JWK key is available locally, then `JsonWebKeySet` is refreshed by fetching the current key set from the JWK endpoint. +The `JsonWebKeySet` refresh can be repeated only after the `quarkus.oidc.token.forced-jwk-refresh-interval` expires. +The default expiry time is 10 minutes. +If no matching JWK key is available after the refresh, the JWT token is sent to the OIDC provider's token introspection endpoint. -If the token is opaque (it can be a binary token or an encrypted JWT token) then it will always be sent to the OpenID Connect Provider's token introspection endpoint. +If the token is opaque, it can be a binary token or an encrypted JWT token, and it is sent to the OIDC provider's token introspection endpoint. -If you work with JWT tokens only and expect that a matching `JsonWebKey` will always be available (possibly after a key set refresh) then you should disable the token introspection: +If you work only with JWT tokens and expect a matching `JsonWebKey` to always be available, for example, after refreshing a key set, you must disable token introspection, as shown in the following example: [source, properties] ---- @@ -176,7 +184,8 @@ quarkus.oidc.token.allow-jwt-introspection=false quarkus.oidc.token.allow-opaque-token-introspection=false ---- -However, there could be cases where JWT tokens must be verified via the introspection only. It can be forced by configuring an introspection endpoint address only, for example, in case of Keycloak you can do it like this: +There might be cases where JWT tokens must be verified through introspection only, which can be forced by configuring an introspection endpoint address only. +The following properties configuration shows you an example of how you can achieve this with Keycloak: [source, properties] ---- @@ -186,9 +195,11 @@ quarkus.oidc.discovery-enabled=false quarkus.oidc.introspection-path=/protocol/openid-connect/tokens/introspect ---- -An advantage of this indirect enforcement of JWT tokens being only introspected remotely is that two remote call are avoided: a remote OIDC metadata discovery call followed by another remote call fetching the verification keys which will not be used, while its disavantage is that the users need to know the introspection endpoint address and configure it manually. +There are advantages and disadvantages to indirectly enforcing the introspection of JWT tokens remotely. +An advantage is that you eliminate the need for two remote calls: a remote OIDC metadata discovery call followed by another remote call to fetch the verification keys that will not be used. +A disadvantage is that you need to know the introspection endpoint address and configure it manually. -The alternative approach is to allow discovering the OIDC metadata (which is a default option) but require that only the remote JWT introspection is performed: +The alternative approach is to allow the default option of OIDC metadata discovery but also require that only the remote JWT introspection is performed, as shown in the following example: [source, properties] ---- @@ -196,20 +207,25 @@ quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus quarkus.oidc.token.require-jwt-introspection-only=true ---- -An advantage of this approach is that the configuration is simple and easy to understand, while its disavantage is that a remote OIDC metadata discovery call is required to discover an introspection endpoint address (though the verification keys will also not be fetched). +An advantage of this approach is that the configuration is simpler and easier to understand. +A disadvantage is that a remote OIDC metadata discovery call is required to discover an introspection endpoint address even though the verification keys will not be fetched. -Note that `io.quarkus.oidc.TokenIntrospection` (a simple `jakarta.json.JsonObject` wrapper) object will be created and can be either injected or accessed as a SecurityIdentity `introspection` attribute if either JWT or opaque token has been successfully introspected. +The `io.quarkus.oidc.TokenIntrospection` object, which is a simple `jakarta.json.JsonObject`, will be created. +It can be injected or accessed as a `SecurityIdentity` `introspection` attribute providing either the JWT or opaque token has been successfully introspected. [[token-introspection-userinfo-cache]] -=== Token Introspection and UserInfo Cache - -All opaque and sometimes JWT Bearer access tokens have to be remotely introspected. If `UserInfo` is also required then the same access token will be used to do a remote call to OpenID Connect Provider again. So, if `UserInfo` is required and the current access token is opaque then for every such token there will be 2 remote calls done - one to introspect it and one to get UserInfo with it, and if the token is JWT then usually only a single remote call will be needed - to get UserInfo with it. +=== Token introspection and `UserInfo` cache -The cost of making up to 2 remote calls per every incoming bearer or code flow access token can sometimes be problematic. +All opaque and sometimes JWT bearer access tokens must be remotely introspected. +If `UserInfo` is also required, the same access token is used in a subsequent remote call to the OIDC provider. +So, if `UserInfo` is required, and the current access token is opaque, two remote calls are made for every such token; one remote call to introspect the token and another to get `UserInfo`. +If the token is JWT, only a single remote call is needed to introspect the token and to also get `UserInfo`. -If it is the case in your production then it can be recommended that the token introspection and `UserInfo` data are cached for a short period of time, for example, for 3 or 5 minutes. +The cost of making up to two remote calls for every incoming bearer or code flow access token can sometimes be problematic. +If this is the case in production, consider caching the token introspection and `UserInfo` data for a short period, for example, 3 or 5 minutes. -`quarkus-oidc` provides `quarkus.oidc.TokenIntrospectionCache` and `quarkus.oidc.UserInfoCache` interfaces which can be used to implement `@ApplicationScoped` cache implementation which can be used to store and retrieve `quarkus.oidc.TokenIntrospection` and/or `quarkus.oidc.UserInfo` objects, for example: +`quarkus-oidc` provides `quarkus.oidc.TokenIntrospectionCache` and `quarkus.oidc.UserInfoCache` interfaces, usable for `@ApplicationScoped` cache implementation. +Use `@ApplicationScoped` cache implementation to store and retrieve `quarkus.oidc.TokenIntrospection` and/or `quarkus.oidc.UserInfo` objects, as outlined in the following example: [source, java] ---- @@ -221,40 +237,45 @@ public class CustomIntrospectionUserInfoCache implements TokenIntrospectionCache } ---- -Each OIDC tenant can either permit or deny storing its `quarkus.oidc.TokenIntrospection` and/or `quarkus.oidc.UserInfo` data with boolean `quarkus.oidc."tenant".allow-token-introspection-cache` and `quarkus.oidc."tenant".allow-user-info-cache` properties. +Each OIDC tenant can either permit or deny the storing of its `quarkus.oidc.TokenIntrospection` data, `quarkus.oidc.UserInfo` data, or both with boolean `quarkus.oidc."tenant".allow-token-introspection-cache` and `quarkus.oidc."tenant".allow-user-info-cache` properties. -Additionally, `quarkus-oidc` provides a simple default memory based token cache which implements both `quarkus.oidc.TokenIntrospectionCache` and `quarkus.oidc.UserInfoCache` interfaces. +Additionally, `quarkus-oidc` provides a simple default memory-based token cache, which implements both `quarkus.oidc.TokenIntrospectionCache` and `quarkus.oidc.UserInfoCache` interfaces. -It can be activated and configured as follows: +You can configure and activate the OIDC token cache as follows: [source, properties] ---- -# 'max-size' is 0 by default so the cache can be activated by setting 'max-size' to a positive value. +# 'max-size' is 0 by default, so the cache can be activated by setting 'max-size' to a positive value: quarkus.oidc.token-cache.max-size=1000 -# 'time-to-live' specifies how long a cache entry can be valid for and will be used by a cleanup timer. +# 'time-to-live' specifies how long a cache entry can be valid for and will be used by a cleanup timer: quarkus.oidc.token-cache.time-to-live=3M -# 'clean-up-timer-interval' is not set by default so the cleanup timer can be activated by setting 'clean-up-timer-interval'. +# 'clean-up-timer-interval' is not set by default, so the cleanup timer can be activated by setting 'clean-up-timer-interval'. quarkus.oidc.token-cache.clean-up-timer-interval=1M ---- -The default cache uses a token as a key and each entry can have `TokenIntrospection` and/or `UserInfo`. It will only keep up to a `max-size` number of entries. If the cache is full when a new entry is to be added then an attempt will be made to find a space for it by removing a single expired entry. Additionally, the cleanup timer, if activated, will periodically check for the expired entries and remove them. +The default cache uses a token as a key, and each entry can have `TokenIntrospection`, `UserInfo`, or both. +It will only keep up to a `max-size` number of entries. +If the cache is already full when a new entry is to be added, an attempt is made to find a space by removing a single expired entry. +Additionally, the cleanup timer, if activated, periodically checks for expired entries and removes them. -Please experiment with the default cache implementation or register a custom one. +You can experiment with the default cache implementation or register a custom one. [[jwt-claim-verification]] -=== JSON Web Token Claim Verification +=== JSON web token claim verification -Once the bearer JWT token's signature has been verified and its `expires at` (`exp`) claim has been checked, the `iss` (`issuer`) claim value is verified next. +When the bearer JWT token's signature has been verified and its `expires at` (`exp`) claim has been checked, the `iss` (`issuer`) claim value is verified next. -By default, the `iss` claim value is compared to the `issuer` property which may have been discovered in the well-known provider configuration. -But if `quarkus.oidc.token.issuer` property is set then the `iss` claim value is compared to it instead. +By default, the `iss` claim value is compared to the `issuer` property, which might have been discovered in the well-known provider configuration. +However, if the `quarkus.oidc.token.issuer` property is set, then the `iss` claim value is compared to it instead. -In some cases, this `iss` claim verification may not work. For example, if the discovered `issuer` property contains an internal HTTP/IP address while the token `iss` claim value contains an external HTTP/IP address. Or when a discovered `issuer` property contains the template tenant variable but the token `iss` claim value has the complete tenant-specific issuer value. +In some cases, this `iss` claim verification might not work. +For example, if the discovered `issuer` property has an internal HTTP/IP address while the token `iss` claim value has an external HTTP/IP address or when a discovered `issuer` property has the template tenant variable, but the token `iss` claim value has the complete tenant-specific issuer value. -In such cases you may want to consider skipping the issuer verification by setting `quarkus.oidc.token.issuer=any`. Please note that it is not recommended and should be avoided unless no other options are available: +Consider skipping the issuer verification by setting `quarkus.oidc.token.issuer=any` in such cases. +Only skip the issuer verification if no other options are available: -- If you work with Keycloak and observe the issuer verification errors due to the different host addresses then configure Keycloak with a `KEYCLOAK_FRONTEND_URL` property to ensure the same host address is used. -- If the `iss` property is tenant specific in a multi-tenant deployment then you can use the `SecurityIdentity` `tenant-id` attribute to check the issuer is correct in the endpoint itself or the custom Jakarta REST filter, for example: +- If you are using Keycloak and observe issuer verification errors caused by different host addresses, configure Keycloak with a `KEYCLOAK_FRONTEND_URL` property to ensure the same host address is used. +- If the `iss` property is tenant-specific in a multitenant deployment, use the `SecurityIdentity` `tenant-id` attribute to check that the issuer is correct in the endpoint itself or the custom JAX-RS filter. [source, java] ---- @@ -284,16 +305,18 @@ public class IssuerValidator implements ContainerRequestFilter { } ---- -Note it is also recommended to use `quarkus.oidc.token.audience` property to verify the token `aud` (`audience`) claim value. +[NOTE] +==== +Consider using the `quarkus.oidc.token.audience` property to verify the token `aud` (audience) claim value. +==== [[single-page-applications]] -=== Single Page Applications +=== Single-page applications -Single Page Application (SPA) typically uses `XMLHttpRequest`(XHR) and the JavaScript utility code provided by the OpenID Connect provider to acquire a bearer token and use it -to access Quarkus `service` applications. - -For example, here is how you can use `keycloak.js` to authenticate the users and refresh the expired tokens from the SPA: +A single-page application (SPA) typically uses `XMLHttpRequest`(XHR) and the JavaScript utility code provided by the OIDC provider to acquire a bearer token to access Quarkus `service` applications. +.Example +You can use `keycloak.js` to authenticate users and refresh the expired tokens from the SPA: [source,html] ---- @@ -334,18 +357,20 @@ For example, here is how you can use `keycloak.js` to authenticate the users and ---- -=== Cross Origin Resource Sharing +=== Cross-origin resource sharing -If you plan to consume your OpenID Connect `service` application from a Single Page Application running on a different domain, you will need to configure CORS (Cross-Origin Resource Sharing). Please read the xref:security-cors.adoc#cors-filter[CORS filter] section of the "Cross-origin resource sharing" guide for more details. +If you plan to use your OpenID Connect `service` application from a single-page application running on a different domain, configure cross-origin resource sharing (CORS). +For more information, see the xref:security-cors.adoc#cors-filter[CORS filter] section of the "Cross-origin resource sharing" guide documentation. -=== Provider Endpoint configuration +=== Provider endpoint configuration -OIDC `service` application needs to know OpenID Connect provider's token, `JsonWebKey` (JWK) set and possibly `UserInfo` and introspection endpoint addresses. +An OIDC `service` application needs to know the OIDC provider's token, `JsonWebKey` (JWK) set, and possibly `UserInfo` and introspection endpoint addresses. By default, they are discovered by adding a `/.well-known/openid-configuration` path to the configured `quarkus.oidc.auth-server-url`. -Alternatively, if the discovery endpoint is not available, or if you would like to save on the discovery endpoint round-trip, you can disable the discovery and configure them with relative path values, for example: +Alternatively, if the discovery endpoint is unavailable, or if you want to save on the discovery endpoint round-trip, you can disable the discovery and configure them with relative path values. +.Example [source, properties] ---- quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus @@ -360,20 +385,23 @@ quarkus.oidc.user-info-path=/protocol/openid-connect/userinfo quarkus.oidc.introspection-path=/protocol/openid-connect/tokens/introspect ---- -=== Token Propagation +[[oidc-token-propagation]] +=== Token propagation -Please see xref:security-openid-connect-client-reference.adoc#token-propagation[Token Propagation] section about the Bearer access token propagation to the downstream services. +For information about bearer access token propagation to the downstream services, see the xref:security-openid-connect-client-reference.adoc#token-propagation[Token Propagation] section of the Quarkus "OpenID Connect (OIDC) and OAuth2 client and filters reference" guide. [[oidc-provider-authentication]] -=== Oidc Provider Client Authentication +=== OIDC provider client authentication -`quarkus.oidc.runtime.OidcProviderClient` is used when a remote request to an OpenID Connect Provider has to be done. If the bearer token has to be introspected then `OidcProviderClient` has to authenticate to the OpenID Connect Provider. Please see xref:security-oidc-code-flow-authentication.adoc#oidc-provider-client-authentication[OidcProviderClient Authentication] for more information about all the supported authentication options. +`quarkus.oidc.runtime.OidcProviderClient` is used when a remote request to an OIDC provider is required. +If introspection of the bearer token is necessary, then `OidcProviderClient` must authenticate to the OIDC provider. +For information about supported authentication options, see the xref:security-oidc-code-flow-authentication.adoc#oidc-provider-client-authentication[OidcProviderClient Authentication] section in the Quarkus "OpenID Connect authorization code flow mechanism for protecting web applications" guide. [[integration-testing]] === Testing -Start by adding the following dependencies to your test project: - +You can begin testing by adding the following dependencies to your test project: +==== [source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] .pom.xml ---- @@ -395,12 +423,19 @@ Start by adding the following dependencies to your test project: testImplementation("io.rest-assured:rest-assured") testImplementation("io.quarkus:quarkus-junit5") ---- +==== + +//@sberyozkin - It might be good to add a stament here about the different mechanisms or methods to integration testing. For example, WireMock... etc + [[integration-testing-wiremock]] -==== Wiremock +==== WireMock -Add the following dependencies to your test project: +You can also use link:https://wiremock.org/[WireMock] for integration testing. +. Add the following dependencies to your test project: ++ +==== [source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] .pom.xml ---- @@ -416,9 +451,11 @@ Add the following dependencies to your test project: ---- testImplementation("io.quarkus:quarkus-test-oidc-server") ---- - -Prepare the REST test endpoint, set `application.properties`, for example: - +==== ++ +. Prepare the REST test endpoint and set `application.properties`, as shown in the following example: ++ +==== [source, properties] ---- # keycloak.url is set by OidcWiremockTestResource @@ -426,9 +463,10 @@ quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/ quarkus.oidc.client-id=quarkus-service-app quarkus.oidc.application-type=service ---- - -and finally write the test code, for example: - +==== +. Finally, write the test code, as shown in the following example: ++ +==== [source, java] ---- import static org.hamcrest.Matchers.equalTo; @@ -453,7 +491,7 @@ public class BearerTokenAuthorizationTest { .when().get("/api/users/me") .then() .statusCode(200) - // the test endpoint returns the name extracted from the injected SecurityIdentity Principal + // The test endpoint returns the name extracted from the injected `SecurityIdentity` principal. .body("userName", equalTo("alice")); } @@ -466,18 +504,17 @@ public class BearerTokenAuthorizationTest { } } ---- +==== +. The `quarkus-test-oidc-server` extension includes a signing RSA private key file in a `JSON Web Key` (`JWK`) format and points to it with a `smallrye.jwt.sign.key.location` configuration property. +You can sign the token by using a no-argument `sign()` operation. -Note that the `quarkus-test-oidc-server` extension includes a signing RSA private key file in a `JSON Web Key` (`JWK`) format and points to it with a `smallrye.jwt.sign.key.location` configuration property. It allows to use a no argument `sign()` operation to sign the token. - -Testing your `quarkus-oidc` `service` application with `OidcWiremockTestResource` provides the best coverage as even the communication channel is tested against the Wiremock HTTP stubs. -`OidcWiremockTestResource` will be enhanced going forward to support more complex bearer token test scenarios. - -If there is an immediate need for a test to define Wiremock stubs not currently supported by `OidcWiremockTestResource` -one can do so via a `WireMockServer` instance injected into the test class, for example: +Testing your `quarkus-oidc` `service` application with `OidcWiremockTestResource` provides the best coverage because even the communication channel is tested against the WireMock HTTP stubs. +It is anticipated that `OidcWiremockTestResource` will be enhanced in an upcoming release to support more complex bearer token test scenarios. +In the meantime, if you need to run a test with WireMock stubs that are not yet supported by `OidcWiremockTestResource`, you can inject a `WireMockServer` instance into the test class, as shown in the following example: [NOTE] ==== -`OidcWiremockTestResource` does not work with `@QuarkusIntegrationTest` against Docker containers, because the Wiremock server is running in the JVM running the test, which cannot be accessed from the Docker container running the Quarkus application. +`OidcWiremockTestResource` does not work with `@QuarkusIntegrationTest` against Docker containers because the WireMock server runs in the JVM that runs the test, which is inaccessible from the Quarkus application Docker container. ==== [source, java] @@ -584,11 +621,13 @@ This test code acquires a token using a `password` grant from the test `Auth0` d [[integration-testing-keycloak-devservices]] ==== Dev Services for Keycloak -Using xref:security-openid-connect-dev-services.adoc[Dev Services for Keycloak] is recommended for the integration testing against Keycloak. -`Dev Services for Keycloak` will launch and initialize a test container: it will create a `quarkus` realm, a `quarkus-app` client (`secret` secret) and add `alice` (`admin` and `user` roles) and `bob` (`user` role) users, where all of these properties can be customized. - -First you need to add the following dependency: +Consider using xref:security-openid-connect-dev-services.adoc[Dev Services for Keycloak] for integration testing against Keycloak. +`Dev Services for Keycloak` will start and initialize a test container. +Then, it will create a `quarkus` realm and a `quarkus-app` client (`secret` secret) and add `alice` (`admin` and `user` roles) and `bob` (`user` role) users, where all of these properties can be customized. +. First, add the following dependency, which provides a utility class `io.quarkus.test.keycloak.client.KeycloakTestClient` that you can use in tests for acquiring the access tokens: ++ +==== [source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] .pom.xml ---- @@ -598,33 +637,40 @@ First you need to add the following dependency: test ---- - +==== ++ +==== [source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] .build.gradle ---- testImplementation("io.quarkus:quarkus-test-keycloak-server") ---- - -which provides a utility class `io.quarkus.test.keycloak.client.KeycloakTestClient` you can use in tests for acquiring the access tokens. - -Next prepare your `application.properties`. You can start with a completely empty `application.properties` as `Dev Services for Keycloak` will register `quarkus.oidc.auth-server-url` pointing to the running test container as well as `quarkus.oidc.client-id=quarkus-app` and `quarkus.oidc.credentials.secret=secret`. - -But if you already have all the required `quarkus-oidc` properties configured then you only need to associate `quarkus.oidc.auth-server-url` with the `prod` profile for `Dev Services for Keycloak`to start a container, for example: - +==== ++ +. Next, prepare your `application.properties` configuration file. +You can start with an empty `application.properties` file because `Dev Services for Keycloak` registers `quarkus.oidc.auth-server-url` and points it to the running test container, `quarkus.oidc.client-id=quarkus-app`, and `quarkus.oidc.credentials.secret=secret`. +. *Optional*: If you have already configured the required `quarkus-oidc` properties, you will need only to associate `quarkus.oidc.auth-server-url` with the `prod` profile for `Dev Services for Keycloak` to start a container, as shown in the following example: ++ +==== [source,properties] ---- %prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus ---- - -If a custom realm file has to be imported into Keycloak before running the tests then you can configure `Dev Services for Keycloak` as follows: - +==== ++ +. If a custom realm file must be imported into Keycloak before running the tests, configure `Dev Services for Keycloak` as follows: ++ +==== [source,properties] ---- %prod.quarkus.oidc.auth-server-url=http://localhost:8180/realms/quarkus quarkus.keycloak.devservices.realm-path=quarkus-realm.json ---- +==== ++ +. Finally, write your test, as outlined in the following examples: -Finally, write your test which will be executed in JVM mode: +.Example of a test executed in JVM mode: [source,java] ---- @@ -658,7 +704,7 @@ public class BearerTokenAuthenticationTest { } ---- -and in native mode: +.Example of a test executed in native mode: [source,java] ---- @@ -671,16 +717,26 @@ public class NativeBearerTokenAuthenticationIT extends BearerTokenAuthentication } ---- -Please see xref:security-openid-connect-dev-services.adoc[Dev Services for Keycloak] for more information about the way it is initialized and configured. +For more information about initializing and configuring Dev Services for Keycloak, see the xref:security-openid-connect-dev-services.adoc[Dev Services for Keycloak] guide. [[integration-testing-keycloak]] + ==== KeycloakTestResourceLifecycleManager -If you need to do some integration testing against Keycloak then you are encouraged to do it with <>. -Use `KeycloakTestResourceLifecycleManager` for your tests only if there is a good reason not to use `Dev Services for Keycloak`. -Start with adding the following dependency: +You can also use `KeycloakTestResourceLifecycleManager` for integration testing with Keycloak. + +[IMPORTANT] +==== +Where possible, use the method described in <> instead of using `KeycloakTestResourceLifecycleManager`, unless you have specific requirements for using this method. +==== + +The following example provides `io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager`, which is an implementaton of `io.quarkus.test.common.QuarkusTestResourceLifecycleManager` that starts a Keycloak container. + +. To start integration testing, add the following dependency: ++ +==== [source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] .pom.xml ---- @@ -696,18 +752,18 @@ Start with adding the following dependency: ---- testImplementation("io.quarkus:quarkus-test-keycloak-server") ---- - -which provides `io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager` - an implementation of `io.quarkus.test.common.QuarkusTestResourceLifecycleManager` which starts a Keycloak container. - -And configure the Maven Surefire plugin as follows: - +==== ++ +. Configure the Maven Surefire plugin as follows, or similarly with `maven.failsafe.plugin` for native image testing: ++ +==== [source,xml] ---- maven-surefire-plugin - + ${keycloak.docker.image}