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 bc2a4fcecd3438..6491a88c37d524 100644 --- a/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc +++ b/docs/src/main/asciidoc/security-oidc-bearer-token-authentication.adoc @@ -1244,6 +1244,81 @@ If you set `quarkus.oidc.client-id`, but your endpoint does not require remote a Quarkus `web-app` applications always require the `quarkus.oidc.client-id` property. ==== +== Mutual TLS token binding + +https://datatracker.ietf.org/doc/html/rfc8705[RFC8705] describes a mechanism for binding access tokens to Mutual TLS (mTLS) client authentication certificates. +It requires that a client certificate's SHA256 thumbprint matches a JWT token or token introspection confirmation `x5t#S256` certificate thumbprint. + +For example, see https://datatracker.ietf.org/doc/html/rfc8705#section-3.1[JWT Certificate Thumbprint Confirmation Method] and https://datatracker.ietf.org/doc/html/rfc8705#section-3.2[Confirmation Method for Token Introspection] sections of https://datatracker.ietf.org/doc/html/rfc8705[RFC8705]. + +MTLS token binding supports a `holder of key` concept, and can be used to confirm that the current access token was issued to the current authenticated client who presents this token. + +When you use both mTLS and OIDC bearer authentication mechanisms, you can enforce that the access tokens must be certificate bound with a single property, after configuring your Quarkus endpoint and Quarkus OIDC to require the use of mTLS. + +For example: + +[source,properties] +---- +quarkus.oidc.auth-server-url=${your_oidc_provider_url} +quarkus.oidc.token.certificate-bound=true <1> +quarkus.oidc.tls.tls-configuration-name=oidc-client-tls <2> + +quarkus.tls.oidc-client-tls.key-store.p12.path=target/certificates/oidc-client-keystore.p12 <2> +quarkus.tls.oidc-client-tls.key-store.p12.password=password +quarkus.tls.oidc-client-tls.trust-store.p12.path=target/certificates/oidc-client-truststore.p12 +quarkus.tls.oidc-client-tls.trust-store.p12.password=password + +quarkus.http.tls-configuration-name=oidc-server-mtls <3> +quarkus.tls.oidc-server-mtls.key-store.p12.path=target/certificates/oidc-keystore.p12 +quarkus.tls.oidc-server-mtls.key-store.p12.password=password +quarkus.tls.oidc-server-mtls.trust-store.p12.path=target/certificates/oidc-server-truststore.p12 +quarkus.tls.oidc-server-mtls.trust-store.p12.password=password +---- +<1> Require that bearer access tokens must be bound to the client certificates. +<2> TLS registry configuration for Quarkus OIDC be able to communicate with the OIDC provider over MTLS +<3> TLS registry configuration requiring external clients to authenticate to the Quarkus endpoint over MTLS + +The above configuration is sufficient to require that OIDC bearer tokens are bound to the client certificates. + +Next, if you need to access both mTLS and OIDC bearer security identities, consider enabling xref:security-authentication-mechanisms#combining-authentication-mechanisms[Inclusive authentication] with `quarkus.http.auth.inclusive=true`. + +Now you can access both MTLS and OIDC security identities as follows: + +[source,java] +---- +package io.quarkus.it.oidc; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +import org.eclipse.microprofile.jwt.JsonWebToken; +import io.quarkus.security.Authenticated; +import io.quarkus.security.credential.CertificateCredential; +import io.quarkus.security.identity.SecurityIdentity; + +@Path("/service") +@Authenticated +public class OidcMtlsEndpoint { + + @Inject + SecurityIdentity mtlsIdentity; <1> + + @Inject + JsonWebToken oidcAccessToken; <2> + + @GET + public String getIdentities() { + var cred = identity.getCredential(CertificateCredential.class).getCertificate(); + return "Identities: " + cred.getSubjectX500Principal().getName().split(",")[0] + + ", " + accessToken.getName(); + } +} +---- +<1> `SecurityIdentity` always represents the primary mTLS authentication when mTLS is used and an inclusive authentication is enabled. +<2> OIDC security identity is also available because enabling an inclusive authentication requires all registered mechanisms to produce the security identity. + + == Authentication after an HTTP request has completed Sometimes, `SecurityIdentity` for a given token must be created when there is no active HTTP request context. diff --git a/extensions/devservices/keycloak/src/main/java/io/quarkus/devservices/keycloak/KeycloakDevServicesProcessor.java b/extensions/devservices/keycloak/src/main/java/io/quarkus/devservices/keycloak/KeycloakDevServicesProcessor.java index c317f8fc6456d5..6227b263b3125d 100644 --- a/extensions/devservices/keycloak/src/main/java/io/quarkus/devservices/keycloak/KeycloakDevServicesProcessor.java +++ b/extensions/devservices/keycloak/src/main/java/io/quarkus/devservices/keycloak/KeycloakDevServicesProcessor.java @@ -303,7 +303,7 @@ private static Map prepareConfiguration( BuildProducer keycloakBuildItemBuildProducer, String internalURL, String hostURL, List realmReps, List errors, KeycloakDevServicesConfigurator devServicesConfigurator, String internalBaseUrl) { - final String realmName = realmReps != null && !realmReps.isEmpty() ? realmReps.iterator().next().getRealm() + final String realmName = !realmReps.isEmpty() ? realmReps.iterator().next().getRealm() : getDefaultRealmName(); final String authServerInternalUrl = realmsURL(internalURL, realmName); @@ -320,29 +320,32 @@ private static Map prepareConfiguration( List realmNames = new LinkedList<>(); - // this needs to be only if we actually start the dev-service as it adds a shutdown hook - // whose TCCL is the Augmentation CL, which if not removed, causes a massive memory leaks - if (vertxInstance == null) { - vertxInstance = Vertx.vertx(); - } + if (createDefaultRealm || !realmReps.isEmpty()) { - WebClient client = createWebClient(vertxInstance); - try { - String adminToken = getAdminToken(client, clientAuthServerBaseUrl); - if (createDefaultRealm) { - createDefaultRealm(client, adminToken, clientAuthServerBaseUrl, users, oidcClientId, oidcClientSecret, errors, - devServicesConfigurator); - realmNames.add(realmName); - } else { - if (realmReps != null) { + // this needs to be only if we actually start the dev-service as it adds a shutdown hook + // whose TCCL is the Augmentation CL, which if not removed, causes a massive memory leaks + if (vertxInstance == null) { + vertxInstance = Vertx.vertx(); + } + + WebClient client = createWebClient(vertxInstance); + try { + String adminToken = getAdminToken(client, clientAuthServerBaseUrl); + if (createDefaultRealm) { + createDefaultRealm(client, adminToken, clientAuthServerBaseUrl, users, oidcClientId, oidcClientSecret, + errors, + devServicesConfigurator); + realmNames.add(realmName); + } else if (realmReps != null) { for (RealmRepresentation realmRep : realmReps) { createRealm(client, adminToken, clientAuthServerBaseUrl, realmRep, errors); realmNames.add(realmRep.getRealm()); } } + + } finally { + client.close(); } - } finally { - client.close(); } Map configProperties = new HashMap<>(); @@ -427,7 +430,7 @@ private static RunningDevService startContainer( // TODO: this probably needs to be addressed String sharedContainerUrl = getSharedContainerUrl(containerAddress); Map configs = prepareConfiguration(keycloakBuildItemBuildProducer, sharedContainerUrl, - sharedContainerUrl, null, errors, devServicesConfigurator, sharedContainerUrl); + sharedContainerUrl, List.of(), errors, devServicesConfigurator, sharedContainerUrl); return new RunningDevService(KEYCLOAK_CONTAINER_NAME, containerAddress.getId(), null, configs); }) .orElseGet(defaultKeycloakContainerSupplier); diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java index 8dc67f4f41e115..5fb2e9f7f40c9f 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcConstants.java @@ -85,4 +85,7 @@ public final class OidcConstants { public static final String CLIENT_METADATA_POST_LOGOUT_URIS = "post_logout_redirect_uris"; public static final String CLIENT_METADATA_SECRET_EXPIRES_AT = "client_secret_expires_at"; public static final String CLIENT_METADATA_ID_ISSUED_AT = "client_id_issued_at"; + + public static final String CONFIRMATION_CLAIM = "cnf"; + public static final String X509_SHA256_THUMBPRINT = "x5t#S256"; } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java index 2111b756bb59eb..31307611087f74 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/OidcTenantConfig.java @@ -2340,6 +2340,15 @@ public static Token fromAudience(String... audience) { */ public Optional verifyAccessTokenWithUserInfo = Optional.empty(); + /** + * If a bearer access token must be bound to the client mTLS certificate. + * It requires that JWT tokens must contain a confirmation `cnf` claim with a SHA256 certificate thumbprint + * matching the client mTLS certificate's SHA256 certificate thumbprint. + *

+ * For opaque tokens, SHA256 certificate thumbprint must be returned in their introspection response. + */ + boolean certificateBound = false; + public Optional isVerifyAccessTokenWithUserInfo() { return verifyAccessTokenWithUserInfo; } @@ -2436,6 +2445,10 @@ public void setAllowOpaqueTokenIntrospection(boolean allowOpaqueTokenIntrospecti this.allowOpaqueTokenIntrospection = allowOpaqueTokenIntrospection; } + public boolean certificateBound() { + return certificateBound; + } + public Optional getAge() { return age; } @@ -2530,6 +2543,7 @@ private void addConfigMappingValues(io.quarkus.oidc.runtime.OidcTenantConfig.Tok allowOpaqueTokenIntrospection = mapping.allowOpaqueTokenIntrospection(); customizerName = mapping.customizerName(); verifyAccessTokenWithUserInfo = mapping.verifyAccessTokenWithUserInfo(); + certificateBound = mapping.certificateBound(); } @Override diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java index f176785c8864ae..fcc0405d0b6d1a 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/BearerAuthenticationMechanism.java @@ -2,14 +2,20 @@ import static io.quarkus.oidc.runtime.OidcUtils.extractBearerToken; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.util.function.Function; +import javax.net.ssl.SSLPeerUnverifiedException; + import org.jboss.logging.Logger; import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponseStatus; import io.quarkus.oidc.AccessTokenCredential; import io.quarkus.oidc.OidcTenantConfig; +import io.quarkus.oidc.common.runtime.OidcConstants; +import io.quarkus.security.AuthenticationFailedException; import io.quarkus.security.identity.IdentityProviderManager; import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.vertx.http.runtime.security.ChallengeData; @@ -25,12 +31,38 @@ public Uni authenticate(RoutingContext context, String token = extractBearerToken(context, oidcTenantConfig); // if a bearer token is provided try to authenticate if (token != null) { + try { + setCertificateThumbprint(context, oidcTenantConfig); + } catch (AuthenticationFailedException ex) { + return Uni.createFrom().failure(ex); + } return authenticate(identityProviderManager, context, new AccessTokenCredential(token)); } LOG.debug("Bearer access token is not available"); return Uni.createFrom().nullItem(); } + private static void setCertificateThumbprint(RoutingContext context, OidcTenantConfig oidcTenantConfig) { + if (oidcTenantConfig.token().certificateBound()) { + Certificate cert = getCertificate(context); + if (!(cert instanceof X509Certificate)) { + LOG.warn("Access token must be bound to X509 client certiifcate"); + throw new AuthenticationFailedException(); + } + context.put(OidcConstants.X509_SHA256_THUMBPRINT, + TrustStoreUtils.calculateThumprint((X509Certificate) cert)); + } + } + + private static Certificate getCertificate(RoutingContext context) { + try { + return context.request().sslSession().getPeerCertificates()[0]; + } catch (SSLPeerUnverifiedException e) { + LOG.warn("Access token must be certificate bound but no client certificate is available"); + throw new AuthenticationFailedException(); + } + } + public Uni getChallenge(RoutingContext context) { Uni tenantContext = resolver.resolveContext(context); return tenantContext.onItem().transformToUni(new Function>() { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java index 673757cbd291df..1247465f01c137 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcIdentityProvider.java @@ -197,9 +197,49 @@ private Uni verifyPrimaryTokenUni(Map r return verifySelfSignedTokenUni(resolvedContext, request.getToken().getToken()); } } else { - return verifyTokenUni(requestData, resolvedContext, request.getToken(), - isIdToken(request), userInfo); + final boolean idToken = isIdToken(request); + Uni result = verifyTokenUni(requestData, resolvedContext, request.getToken(), idToken, + userInfo); + if (!idToken && resolvedContext.oidcConfig().token().certificateBound()) { + return result.onItem().transform(new Function() { + + @Override + public TokenVerificationResult apply(TokenVerificationResult t) { + String tokenCertificateThumbprint = getTokenCertThumbprint(requestData, t); + if (tokenCertificateThumbprint == null) { + LOG.warn( + "Access token does not contain a confirmation 'cnf' claim with the certificate thumbprint"); + throw new AuthenticationFailedException(); + } + String clientCertificateThumbprint = (String) requestData.get(OidcConstants.X509_SHA256_THUMBPRINT); + if (clientCertificateThumbprint == null) { + LOG.warn("Client certificate thumbprint is not available"); + throw new AuthenticationFailedException(); + } + if (!clientCertificateThumbprint.equals(tokenCertificateThumbprint)) { + LOG.warn("Client certificate thumbprint does not match the token certificate thumbprint"); + throw new AuthenticationFailedException(); + } + return t; + } + + }); + } else { + return result; + } + } + } + + private static String getTokenCertThumbprint(Map requestData, TokenVerificationResult t) { + JsonObject json = t.localVerificationResult != null ? t.localVerificationResult + : new JsonObject(t.introspectionResult.getIntrospectionString()); + JsonObject cnf = json.getJsonObject(OidcConstants.CONFIRMATION_CLAIM); + String thumbprint = cnf == null ? null : cnf.getString(OidcConstants.X509_SHA256_THUMBPRINT); + if (thumbprint != null) { + requestData.put((t.introspectionResult == null ? OidcUtils.JWT_THUMBPRINT : OidcUtils.INTROSPECTION_THUMBPRINT), + true); } + return thumbprint; } private Uni getUserInfoAndCreateIdentity(Uni tokenUni, diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTenantConfig.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTenantConfig.java index f3c9c1463458c3..35ad3ce4bafcfb 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTenantConfig.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcTenantConfig.java @@ -1106,6 +1106,16 @@ interface Token { @ConfigDocDefault("false") Optional verifyAccessTokenWithUserInfo(); + /** + * If a bearer access token must be bound to the client mTLS certificate. + * It requires that JWT tokens must contain a confirmation `cnf` claim with a SHA256 certificate thumbprint + * matching the client mTLS certificate's SHA256 certificate thumbprint. + *

+ * For opaque tokens, SHA256 certificate thumbprint must be returned in their introspection response. + */ + @WithDefault("false") + boolean certificateBound(); + } enum ApplicationType { diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java index 486ae0548e5896..d7e4498ff9f9e5 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcUtils.java @@ -94,6 +94,8 @@ public final class OidcUtils { public static final String SESSION_AT_COOKIE_NAME = SESSION_COOKIE_NAME + ACCESS_TOKEN_COOKIE_SUFFIX; public static final String SESSION_RT_COOKIE_NAME = SESSION_COOKIE_NAME + REFRESH_TOKEN_COOKIE_SUFFIX; public static final String STATE_COOKIE_NAME = "q_auth"; + public static final String JWT_THUMBPRINT = "jwt_thumbprint"; + public static final String INTROSPECTION_THUMBPRINT = "introspection_thumbprint"; // Browsers enforce that the total Set-Cookie expression such as // `q_session_tenant-a=,Path=/somepath,Expires=...` does not exceed 4096 diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/TokenConfigBuilder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/TokenConfigBuilder.java index cefe25f9dd82a7..19990b44b50e00 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/TokenConfigBuilder.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/builders/TokenConfigBuilder.java @@ -25,7 +25,7 @@ private record TokenImpl(Optional issuer, Optional> audienc String authorizationScheme, Optional signatureAlgorithm, Optional decryptionKeyLocation, boolean allowJwtIntrospection, boolean requireJwtIntrospectionOnly, boolean allowOpaqueTokenIntrospection, Optional customizerName, - Optional verifyAccessTokenWithUserInfo) implements OidcTenantConfig.Token { + Optional verifyAccessTokenWithUserInfo, boolean certificateBound) implements OidcTenantConfig.Token { } private final OidcTenantConfigBuilder builder; @@ -50,6 +50,7 @@ private record TokenImpl(Optional issuer, Optional> audienc private boolean allowOpaqueTokenIntrospection; private Optional customizerName; private Optional verifyAccessTokenWithUserInfo; + private boolean certificateBound; public TokenConfigBuilder() { this(new OidcTenantConfigBuilder()); @@ -83,6 +84,7 @@ public TokenConfigBuilder(OidcTenantConfigBuilder builder) { this.allowOpaqueTokenIntrospection = token.allowOpaqueTokenIntrospection(); this.customizerName = token.customizerName(); this.verifyAccessTokenWithUserInfo = token.verifyAccessTokenWithUserInfo(); + this.certificateBound = token.certificateBound(); } /** @@ -371,6 +373,24 @@ public TokenConfigBuilder verifyAccessTokenWithUserInfo(boolean verifyAccessToke return this; } + /** + * Sets {@link OidcTenantConfig.Token#certificateBound()} to true. + * + * @return this builder + */ + public TokenConfigBuilder certificateBound() { + return certificateBound(true); + } + + /** + * @param certificateBound {@link OidcTenantConfig.Token#certificateBound()} + * @return this builder + */ + public TokenConfigBuilder certificateBound(boolean certificateBound) { + this.certificateBound = certificateBound; + return this; + } + /** * @return built {@link OidcTenantConfig.Token} */ @@ -381,7 +401,7 @@ public OidcTenantConfig.Token build() { lifespanGrace, age, issuedAtRequired, principalClaim, refreshExpired, refreshTokenTimeSkew, forcedJwkRefreshInterval, header, authorizationScheme, signatureAlgorithm, decryptionKeyLocation, allowJwtIntrospection, requireJwtIntrospectionOnly, allowOpaqueTokenIntrospection, customizerName, - verifyAccessTokenWithUserInfo); + verifyAccessTokenWithUserInfo, certificateBound); } } diff --git a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcTenantConfigImpl.java b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcTenantConfigImpl.java index 73bc8ddf0671bb..d2be988020b039 100644 --- a/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcTenantConfigImpl.java +++ b/extensions/oidc/runtime/src/test/java/io/quarkus/oidc/runtime/OidcTenantConfigImpl.java @@ -189,6 +189,7 @@ enum ConfigMappingMethods { TOKEN_ALLOW_OPAQUE_TOKEN_INTROSPECTION, TOKEN_CUSTOMIZER_NAME, TOKEN_VERIFY_ACCESS_TOKEN_WITH_USER_INFO, + TOKEN_CERTIFICATE_BOUND, ROLES_ROLE_CLAIM_PATH, ROLES_ROLE_CLAIM_SEPARATOR, ROLES_SOURCE, @@ -438,6 +439,12 @@ public Optional verifyAccessTokenWithUserInfo() { invocationsRecorder.put(ConfigMappingMethods.TOKEN_VERIFY_ACCESS_TOKEN_WITH_USER_INFO, true); return Optional.empty(); } + + @Override + public boolean certificateBound() { + invocationsRecorder.put(ConfigMappingMethods.TOKEN_CERTIFICATE_BOUND, true); + return false; + } }; } diff --git a/integration-tests/oidc-mtls/src/main/java/io/quarkus/it/oidc/OidcMtlsEndpoint.java b/integration-tests/oidc-mtls/src/main/java/io/quarkus/it/oidc/OidcMtlsEndpoint.java index 53319e396b3f86..1b9f0d5dcf0ba5 100644 --- a/integration-tests/oidc-mtls/src/main/java/io/quarkus/it/oidc/OidcMtlsEndpoint.java +++ b/integration-tests/oidc-mtls/src/main/java/io/quarkus/it/oidc/OidcMtlsEndpoint.java @@ -6,10 +6,14 @@ import org.eclipse.microprofile.jwt.JsonWebToken; +import io.quarkus.oidc.runtime.OidcUtils; +import io.quarkus.security.Authenticated; import io.quarkus.security.credential.CertificateCredential; import io.quarkus.security.identity.SecurityIdentity; +import io.vertx.ext.web.RoutingContext; @Path("/service") +@Authenticated public class OidcMtlsEndpoint { @Inject @@ -18,11 +22,48 @@ public class OidcMtlsEndpoint { @Inject JsonWebToken accessToken; + @Inject + RoutingContext routingContext; + + @GET + @Path("mtls-jwt") + public String getNameJwt() { + var cred = identity.getCredential(CertificateCredential.class).getCertificate(); + return "Identities: " + cred.getSubjectX500Principal().getName().split(",")[0] + + ", " + accessToken.getName() + "; " + + "Client: " + accessToken.getClaim("azp") + "; " + + "JWT cert thumbprint: " + isJwtTokenThumbprintAvailable() + ", " + + "introspection cert thumbprint: " + isIntrospectionThumbprintAvailable(); + } + + @GET + @Path("mtls-introspection") + public String getNameIntrospection() { + var cred = identity.getCredential(CertificateCredential.class).getCertificate(); + return "Identities: " + cred.getSubjectX500Principal().getName().split(",")[0] + ", " + + accessToken.getName() + "; " + + "Client: " + accessToken.getClaim("azp") + "; " + + "JWT cert thumbprint: " + isJwtTokenThumbprintAvailable() + ", " + + "introspection cert thumbprint: " + isIntrospectionThumbprintAvailable(); + } + @GET - @Path("name") - public String getName() { + @Path("mtls-client-with-secret") + public String getNameMtlsClientWithSecret() { var cred = identity.getCredential(CertificateCredential.class).getCertificate(); return "Identities: " + cred.getSubjectX500Principal().getName().split(",")[0] + ", " - + accessToken.getName(); + + accessToken.getName() + "; " + + "Client: " + accessToken.getClaim("azp") + "; " + + "JWT cert thumbprint: " + isJwtTokenThumbprintAvailable() + ", " + + "introspection cert thumbprint: " + isIntrospectionThumbprintAvailable(); + } + + private boolean isJwtTokenThumbprintAvailable() { + return Boolean.TRUE.equals(routingContext.get(OidcUtils.JWT_THUMBPRINT)); + } + + private boolean isIntrospectionThumbprintAvailable() { + + return Boolean.TRUE.equals(routingContext.get(OidcUtils.INTROSPECTION_THUMBPRINT)); } } diff --git a/integration-tests/oidc-mtls/src/main/resources/application.properties b/integration-tests/oidc-mtls/src/main/resources/application.properties index 939e259a700ac8..396008070a9d38 100644 --- a/integration-tests/oidc-mtls/src/main/resources/application.properties +++ b/integration-tests/oidc-mtls/src/main/resources/application.properties @@ -1,11 +1,57 @@ -quarkus.http.tls-configuration-name=oidc-mtls -quarkus.tls.oidc-mtls.key-store.p12.path=target/certificates/oidc-keystore.p12 -quarkus.tls.oidc-mtls.key-store.p12.password=password -quarkus.tls.oidc-mtls.trust-store.p12.path=target/certificates/oidc-server-truststore.p12 -quarkus.tls.oidc-mtls.trust-store.p12.password=password +# Disable default tenant +quarkus.oidc.tenant-enabled=false + +# Tenant which expects JWT token to contain a certificate thumbprint +quarkus.oidc.mtls-jwt.auth-server-url=${quarkus.oidc.auth-server-url} +quarkus.oidc.mtls-jwt.client-id=backend-service +# Certificate bound +quarkus.oidc.mtls-jwt.token.certificate-bound=true +quarkus.oidc.mtls-jwt.tls.tls-configuration-name=oidc-client-tls + +# Tenant which expects token introspection to contain a certificate thumbprint +quarkus.oidc.mtls-introspection.auth-server-url=${quarkus.oidc.auth-server-url} +quarkus.oidc.mtls-introspection.client-id=backend-service +# Certificate bound +quarkus.oidc.mtls-introspection.token.certificate-bound=true +quarkus.oidc.mtls-introspection.token.require-jwt-introspection-only=true +quarkus.oidc.mtls-introspection.tls.tls-configuration-name=oidc-client-tls + +# Tenant which accepts tokens not bound to certificates +quarkus.oidc.mtls-client-with-secret.auth-server-url=${quarkus.oidc.auth-server-url} +quarkus.oidc.mtls-client-with-secret.tls.tls-configuration-name=oidc-client-tls + +# MTLS TLS registry configuration for Quarkus OIDC to be able to communicate with Keycloak +quarkus.tls.oidc-client-tls.key-store.p12.path=target/certificates/oidc-client-keystore.p12 +quarkus.tls.oidc-client-tls.key-store.p12.password=password +quarkus.tls.oidc-client-tls.trust-store.p12.path=target/certificates/oidc-client-truststore.p12 +quarkus.tls.oidc-client-tls.trust-store.p12.password=password +quarkus.tls.oidc-client-tls.hostname-verification-algorithm=NONE + +# Quarkus endpoint MTLS TLS registry configuration requiring external clients to authenticate with MTLS +quarkus.http.tls-configuration-name=oidc-server-mtls +quarkus.tls.oidc-server-mtls.key-store.p12.path=target/certificates/oidc-keystore.p12 +quarkus.tls.oidc-server-mtls.key-store.p12.password=password +quarkus.tls.oidc-server-mtls.trust-store.p12.path=target/certificates/oidc-server-truststore.p12 +quarkus.tls.oidc-server-mtls.trust-store.p12.password=password + +# Have Keycloak devservice started even though the default tenant is disabled +quarkus.keycloak.devservices.start-with-disabled-tenant=true +# Do not create the default ream and client, since the test factory uploads the realm file +quarkus.keycloak.devservices.create-realm=false +quarkus.keycloak.devservices.create-client=false +# Make Keycloak require MTLS +quarkus.keycloak.devservices.start-command=start --https-client-auth=required --hostname-strict=false --https-key-store-file=/etc/server-keystore.p12 --https-trust-store-file=/etc/server-truststore.p12 --https-trust-store-password=password --spi-user-profile-declarative-user-profile-config-file=/opt/keycloak/upconfig.json +quarkus.keycloak.devservices.resource-aliases.keystore=target/certificates/oidc-keystore.p12 +quarkus.keycloak.devservices.resource-aliases.truststore=target/certificates/oidc-server-truststore.p12 +quarkus.keycloak.devservices.resource-mappings.keystore=/etc/server-keystore.p12 +quarkus.keycloak.devservices.resource-mappings.truststore=/etc/server-truststore.p12 +quarkus.keycloak.devservices.show-logs=true quarkus.http.auth.inclusive=true quarkus.http.ssl.client-auth=REQUIRED quarkus.http.insecure-requests=DISABLED -quarkus.native.additional-build-args=-H:IncludeResources=target/certificates/.*\\.p12 +quarkus.native.additional-build-args=-H:IncludeResources=.*\\.p12 + +quarkus.log.category."io.quarkus.oidc.runtime".min-level=TRACE +quarkus.log.category."io.quarkus.oidc.runtime".level=TRACE diff --git a/integration-tests/oidc-mtls/src/main/resources/quarkus-realm.json b/integration-tests/oidc-mtls/src/main/resources/quarkus-realm.json new file mode 100644 index 00000000000000..519db168561d33 --- /dev/null +++ b/integration-tests/oidc-mtls/src/main/resources/quarkus-realm.json @@ -0,0 +1,2530 @@ +{ + "id": "quarkus", + "realm": "quarkus", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxTemporaryLockouts": 0, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "3ce83241-464b-4ca0-8f0f-17002a797aab", + "name": "admin", + "composite": false, + "clientRole": false, + "containerId": "quarkus", + "attributes": {} + }, + { + "id": "68615956-51ca-49ca-865a-f9cb2571b027", + "name": "confidential", + "composite": false, + "clientRole": false, + "containerId": "quarkus", + "attributes": {} + }, + { + "id": "c6d57a00-eb97-460d-91b0-89e6a94a7aa5", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "quarkus", + "attributes": {} + }, + { + "id": "c50286f6-3562-473f-ad45-9767b982ff45", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "quarkus", + "attributes": {} + }, + { + "id": "ecf82b72-870a-4b33-9c73-57c960c888bd", + "name": "default-roles-quarkus", + "description": "${role_default-roles}", + "composite": false, + "clientRole": false, + "containerId": "quarkus", + "attributes": {} + }, + { + "id": "d3246456-8f5d-4722-8364-a46a8d25dc7c", + "name": "user", + "composite": false, + "clientRole": false, + "containerId": "quarkus", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "4b24739e-3a0a-48d2-b202-713430d775d2", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "1238e880-907f-4e8b-a032-4d09a922adf8", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "bcc6637a-294c-4529-a706-33b8c49f40fc", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-groups", + "query-users" + ] + } + }, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "f65a9a54-d689-4c45-87cd-f177babdeaef", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "183e58f4-136b-4c91-b20a-5c76857a671e", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "9aec187f-d623-45c7-a8b3-5aa32d115f50", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "52521d81-e7d6-4929-95cb-0a084c5bacb8", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "e92c753a-7b17-4adc-9962-04f24040e404", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "1285d11d-08f4-4753-b27e-d5f7b0e76fca", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "b0ee027f-5aa6-48eb-837f-4635590576ec", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "61ac3405-ccbd-4cdf-8cac-c918e1d77e1f", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "f1176efb-e24b-4fab-8b37-8265aefd10e1", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "968be265-6868-416a-91a1-e5bd882349ab", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "e77611fc-5ec5-4438-96c3-b291aae78d0c", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "165b24e1-9488-4cc7-87cd-e74b1cdc5619", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "f5163480-f5fc-4355-8be1-8cc96ff7d99d", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "manage-identity-providers", + "query-clients", + "view-users", + "view-identity-providers", + "view-events", + "view-clients", + "manage-events", + "query-realms", + "manage-clients", + "view-authorization", + "query-groups", + "query-users", + "manage-authorization", + "manage-users", + "manage-realm", + "create-client", + "view-realm", + "impersonation" + ] + } + }, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "64ec1233-2cee-4d9b-ab6f-0bd06702c684", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "6e633885-b1fb-4ca8-9ef9-7c4c8f8732e8", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + }, + { + "id": "683bddad-81c6-4dca-87b6-e14b0b2ae524", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "dd29e998-54e9-4067-884e-4f986e990c1d", + "attributes": {} + } + ], + "backend-client-with-secret": [ + { + "id": "b12a77ee-b342-4ee5-8267-29bcd9ba5fdc", + "name": "uma_protection", + "composite": false, + "clientRole": true, + "containerId": "4023904e-f71d-4498-ac85-a786b871b035", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "backend-service": [ + { + "id": "5b9947c6-eb74-4de6-8623-0285720993f3", + "name": "uma_protection", + "composite": false, + "clientRole": true, + "containerId": "302430aa-3929-42cf-8ba2-2b9d2e71dc3a", + "attributes": {} + } + ], + "broker": [ + { + "id": "bee1f77b-34a9-4386-9eca-eb19db248394", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "2a02328b-6aa6-49a8-b56c-7036c273c70b", + "attributes": {} + } + ], + "account": [ + { + "id": "e2cd9235-026b-4637-aa2e-1bc01a829a1b", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "35b5a50f-a32a-4bd1-b4b3-50f0ade135c7", + "attributes": {} + }, + { + "id": "540d80ab-2eb1-4f1c-84ad-4fe0a84e2d2a", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "35b5a50f-a32a-4bd1-b4b3-50f0ade135c7", + "attributes": {} + }, + { + "id": "d3ffeda8-8d57-4b63-ae1d-90f88bc4b068", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "35b5a50f-a32a-4bd1-b4b3-50f0ade135c7", + "attributes": {} + }, + { + "id": "1ffcc7fe-50a8-4300-b172-10f651e5a5bd", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "35b5a50f-a32a-4bd1-b4b3-50f0ade135c7", + "attributes": {} + }, + { + "id": "74f86380-8e18-407f-ad16-529044f9c7dc", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "35b5a50f-a32a-4bd1-b4b3-50f0ade135c7", + "attributes": {} + }, + { + "id": "87f13502-eb23-4c51-be92-aeda4d9e9f28", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "35b5a50f-a32a-4bd1-b4b3-50f0ade135c7", + "attributes": {} + }, + { + "id": "350c9ea0-5222-47d6-862b-91b34a0a1ba9", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "35b5a50f-a32a-4bd1-b4b3-50f0ade135c7", + "attributes": {} + }, + { + "id": "6bfb0d41-8f2b-4adf-a5a7-e0ba1d1fad10", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "35b5a50f-a32a-4bd1-b4b3-50f0ade135c7", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRole": { + "id": "ecf82b72-870a-4b33-9c73-57c960c888bd", + "name": "default-roles-quarkus", + "description": "${role_default-roles}", + "composite": false, + "clientRole": false, + "containerId": "quarkus" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppFreeOTPName", + "totpAppGoogleName", + "totpAppMicrosoftAuthenticatorName" + ], + "localizationTexts": {}, + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyExtraOrigins": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "webAuthnPolicyPasswordlessExtraOrigins": [], + "users": [ + { + "id": "8587742e-638b-4b6f-8197-5bdba72cd137", + "username": "service-account-backend-client-with-secret", + "emailVerified": false, + "createdTimestamp": 1734350109683, + "enabled": true, + "totp": false, + "serviceAccountClientId": "backend-client-with-secret", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "default-roles-quarkus" + ], + "clientRoles": { + "backend-client-with-secret": [ + "uma_protection" + ] + }, + "notBefore": 0, + "groups": [] + }, + { + "id": "948c59ec-46ed-4d99-aa43-02900029b930", + "username": "service-account-backend-service", + "email": "service-account-backend-service@placeholder.org", + "emailVerified": false, + "createdTimestamp": 1554245880023, + "enabled": true, + "totp": false, + "serviceAccountClientId": "backend-service", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "offline_access" + ], + "clientRoles": { + "backend-service": [ + "uma_protection" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "notBefore": 0, + "groups": [] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account", + "view-groups" + ] + } + ] + }, + "clients": [ + { + "id": "35b5a50f-a32a-4bd1-b4b3-50f0ade135c7", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/quarkus/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [ + "/realms/quarkus/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "d19cba64-7238-44a9-b4b0-cc7705395d39", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/quarkus/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/quarkus/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "e0ac5df3-56e1-428e-9122-4995a298530e", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "c6e812f9-326b-4e66-9197-157a5d43b172", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "4023904e-f71d-4498-ac85-a786b871b035", + "clientId": "backend-client-with-secret", + "name": "", + "description": "", + "rootUrl": "", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "secret", + "redirectUris": [ + "/*" + ], + "webOrigins": [ + "/*" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "client.secret.creation.time": "1734350109", + "backchannel.logout.session.required": "true", + "oauth2.device.authorization.grant.enabled": "false", + "display.on.consent.screen": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "fc141cea-7f20-4e8d-98cb-3ad12271f5f5", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "client_id", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "client_id", + "jsonType.label": "String" + } + }, + { + "id": "000c1afd-626e-4ece-b63a-c389288ed90f", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "314891f7-b541-44f1-9549-2a53a40eeb3c", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "302430aa-3929-42cf-8ba2-2b9d2e71dc3a", + "clientId": "backend-service", + "name": "", + "description": "", + "rootUrl": "", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-x509", + "secret": "**********", + "redirectUris": [ + "*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "x509.subjectdn": "CN=backend-service", + "client.introspection.response.allow.jwt.claim.enabled": "false", + "post.logout.redirect.uris": "+", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "use.refresh.tokens": "true", + "oidc.ciba.grant.enabled": "false", + "client.use.lightweight.access.token.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "tls.client.certificate.bound.access.tokens": "true", + "require.pushed.authorization.requests": "false", + "acr.loa.map": "{}", + "display.on.consent.screen": "false", + "x509.allow.regex.pattern.comparison": "false", + "token.response.type.bearer.lower-case": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "1390addb-ba10-4455-a1ea-8455c3770cf1", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientId", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientId", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "cdafda09-f6d9-41e3-87ef-6789e861689a", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "95b47211-912c-43f5-84ce-5bfbc761325d", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "2a02328b-6aa6-49a8-b56c-7036c273c70b", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "dd29e998-54e9-4067-884e-4f986e990c1d", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "6517b152-0693-4b28-a798-a0deea3e8644", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/quarkus/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/quarkus/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "9c7093a9-4da1-47e4-b2a5-afe180782220", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "1cd1a093-aec5-4949-912d-d5f61fff5705", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "8b5d72e1-cfd6-4832-8432-efedf34c265b", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "83e275f7-b171-45fa-99c7-7c04f91fbe41", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "${rolesScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "9eb470cc-8157-46f2-8233-8cae169c6591", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "eebdefd0-c446-4bf3-b945-08db42f0ea92", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "id": "37c62d93-c670-487c-8c3a-a6329a9924b0", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "7eaa8ede-9a92-487a-9444-60a5d7355542", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "e7616dd3-8886-4d47-8645-74e4565d7606", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "35bfd94e-681f-456a-bca0-0d0d8d986a96", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${addressScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "1f710637-5a3c-45f3-b4d3-74046993e0eb", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "eb0bdf87-6cda-4684-89a8-f7bd6f0c7bba", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${emailScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "1ea39fbb-c692-4a1d-a143-a05b030889cb", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "f97bd1de-6c95-4c5b-804c-f8b354457453", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "58e57c6f-18bf-4347-9ab0-b8325ef522e0", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "5a4a2c20-fef2-40b5-9406-136475442b47", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "97aca0c9-7f14-4783-bb48-681de54f0b31", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "55621a1e-cd6b-45a7-9f06-a678e0801b9c", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "6c4f32b0-8ae4-4b4b-b4fa-a053df0bbb3a", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "userinfo.token.claim": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "2687cb87-1dbf-435c-8ef9-f2fe38127405", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "d20498e8-4ec8-4496-9d8f-c09131dd5d15", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${profileScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "7da35ca7-5c93-4d23-b6b7-761d80c966c8", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "a443a633-7cd2-406d-85f1-6e3d3173eff9", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "d04d2dd6-04fc-4230-90eb-7074056cfdee", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "ef68a07b-ed0a-418b-9c6d-7ecd58946813", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "144acdba-ee08-4349-b806-a4394bd5f351", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "4b435d62-1f62-4513-a131-208318731d7b", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "794b162d-460a-4465-b90d-66dabc4b3cce", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "779b131a-d0cc-420d-90b3-075b19210379", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "0e0f1e8d-60f9-4435-b753-136d70e56af8", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "8451d26b-904d-4858-9db1-87fe137c1172", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "011fe224-355f-4e3c-a3d4-6a325eec561d", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "06f656a1-67f1-4c53-92df-9e5823853191", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "03293b81-5599-4163-81b8-eb05c3d14ed2", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "d21642b7-8190-4de4-8d0d-09b0e505c02c", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "541f2eae-d481-4d00-be30-89f4f60d169f", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${phoneScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "eda935c3-7294-403c-85bd-fee7216af822", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "0b8c0161-5042-4912-a753-c262569ed5bc", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "688a0898-d197-48cb-889a-1e6dc892f69f", + "name": "basic", + "description": "OpenID Connect scope for add all basic claims to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "ff13d88f-ad9f-417d-92b4-8d2b223e556b", + "name": "auth_time", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "AUTH_TIME", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "auth_time", + "jsonType.label": "long" + } + }, + { + "id": "55e915a1-a832-4246-9ac4-7fb777f5efcd", + "name": "sub", + "protocol": "openid-connect", + "protocolMapper": "oidc-sub-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "web-origins", + "role_list", + "roles", + "profile", + "email", + "acr", + "basic" + ], + "defaultOptionalClientScopes": [ + "address", + "phone", + "microprofile-jwt", + "offline_access" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "referrerPolicy": "no-referrer", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "7ebad719-3c5e-4880-a9f1-3242dd9dbe24", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "8fe9bd3a-a11c-4c97-948e-90ba7fbe008f", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-full-name-mapper", + "oidc-usermodel-attribute-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper" + ] + } + }, + { + "id": "e9b76eee-365f-4b5f-80cb-316eb07b36fa", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "8ed9d103-7a79-47b4-9426-9e4a84340d22", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "a07e90f1-5662-4344-8529-f284c361a25e", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "9b4e5b69-1d07-489b-b8a5-07329c957141", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "e2f513d3-44e3-435c-8b2a-68a5d384fd97", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "43a5aac2-b395-4935-94cb-12f4d9b4eb05", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-address-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper", + "saml-user-attribute-mapper", + "oidc-full-name-mapper", + "oidc-usermodel-property-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-property-mapper" + ] + } + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "e813f916-9006-4e55-8f7b-3c174aa13d33", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": { + "kc.user.profile.config": [ + "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}],\"unmanagedAttributePolicy\":\"ENABLED\"}" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "066f8625-06ba-4463-995f-93a058d2d800", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "19c225cc-b499-48b1-aed6-3e1dd5bcf04c", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + }, + { + "id": "d100e4f5-8ef1-4c52-bbdf-5434ddb09268", + "name": "hmac-generated-hs512", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS512" + ] + } + }, + { + "id": "4008d665-26c4-4056-a028-232bc0636029", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "55f3ddc5-0f36-496d-817f-3aa8f426ee45", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "2d0ccc2f-888c-495f-91ae-dfffba572d33", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "b7ff5812-2bc2-4f8f-9913-bd3b97a08618", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "ddbfb446-21d8-44c2-a207-7f83d760e94f", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "21dc8a77-3900-46e7-b1e4-40f5bcbd9b8e", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "329ed4e1-d3a8-42aa-a9ff-991a0e8f2851", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "66b4a633-6ba0-41e2-944f-0b13369c1e78", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "fce169a3-c245-4dc8-a3c5-295bfa7057a4", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "4c5476fa-9aef-440b-bd14-25bf8cbfcd16", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "75d65771-3bfb-4def-a539-656de7d1af58", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "a6a9036b-192e-461f-91c7-d8117435188d", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "f86bdf88-8bee-480b-8e81-67dcd674e46c", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "6f87019e-c995-4049-b8bf-d08a9c3a13f3", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + } + ] + }, + { + "id": "fadc7c73-7fae-4c28-ad69-51bb03ba17bf", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "d930f23e-ae58-45b2-9e01-20691200c926", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "8d62b1dd-6066-454d-bc76-f783d50fecaa", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "f99be349-ce0b-44a4-9f70-73f57cb8c164", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "33ee7503-bd12-4e5a-903c-5ae580f48709", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "6970ebc8-0b24-414c-8544-3cc48b1a0e4c", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "d14b76f4-b608-4b13-b51c-b9e162ad784b", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "TERMS_AND_CONDITIONS", + "name": "Terms and Conditions", + "providerId": "TERMS_AND_CONDITIONS", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "delete_credential", + "name": "Delete Credential", + "providerId": "delete_credential", + "enabled": true, + "defaultAction": false, + "priority": 100, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "firstBrokerLoginFlow": "first broker login", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "5", + "parRequestUriLifespan": "60", + "cibaInterval": "5", + "realmReusableOtpCode": "false" + }, + "users" : [ { + "id" : "af134cab-f41c-4675-b141-205f975db679", + "username" : "admin", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "credentials" : [ { + "type" : "password", + "hashedSaltedValue" : "NICTtwsvSxJ5hL8hLAuleDUv9jwZcuXgxviMXvR++cciyPtiIEStEaJUyfA9DOir59awjPrHOumsclPVjNBplA==", + "salt" : "T/2P5o5oxFJUEk68BRURRg==", + "hashIterations" : 27500, + "counter" : 0, + "algorithm" : "pbkdf2-sha256", + "digits" : 0, + "period" : 0, + "createdDate" : 1554245879354, + "config" : { } + } ], + "disableableCredentialTypes" : [ "password" ], + "requiredActions" : [ ], + "realmRoles" : [ "admin", "user" ], + "notBefore" : 0, + "groups" : [ ] + }, { + "id" : "eb4123a3-b722-4798-9af5-8957f823657a", + "username" : "alice", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "credentials" : [ { + "type" : "password", + "hashedSaltedValue" : "A3okqV2T/ybXTVEgKfosoSjP8Yc9IZbFP/SY4cEd6hag7TABQrQ6nUSuwagGt96l8cw1DTijO75PqX6uiTXMzw==", + "salt" : "sl4mXx6T9FypPH/s9TngfQ==", + "hashIterations" : 27500, + "counter" : 0, + "algorithm" : "pbkdf2-sha256", + "digits" : 0, + "period" : 0, + "createdDate" : 1554245879116, + "config" : { } + } ], + "disableableCredentialTypes" : [ "password" ], + "requiredActions" : [ ], + "realmRoles" : [ "user" ], + "notBefore" : 0, + "groups" : [ ] + }, { + "id" : "1eed6a8e-a853-4597-b4c6-c4c2533546a0", + "username" : "jdoe", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "credentials" : [ { + "type" : "password", + "hashedSaltedValue" : "JV3DUNLjqOadjbBOtC4rvacQI553CGaDGAzBS8MR5ReCr7SwF3E6CsW3T7/XO8ITZAsch8+A/6loeuCoVLLJrg==", + "salt" : "uCbOH7HZtyDtMd0E9DG/nw==", + "hashIterations" : 27500, + "counter" : 0, + "algorithm" : "pbkdf2-sha256", + "digits" : 0, + "period" : 0, + "createdDate" : 1554245879227, + "config" : { } + } ], + "disableableCredentialTypes" : [ "password" ], + "requiredActions" : [ ], + "realmRoles" : [ "confidential", "user" ], + "notBefore" : 0, + "groups" : [ ] + }, + { + "id": "948c59ec-46ed-4d99-aa43-02900029b930", + "username": "service-account-backend-service", + "email": "service-account-backend-service@placeholder.org", + "emailVerified": false, + "createdTimestamp": 1554245880023, + "enabled": true, + "totp": false, + "serviceAccountClientId": "backend-service", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "offline_access" + ], + "clientRoles": { + "backend-service": [ + "uma_protection" + ], + "account": [ + "view-profile", + "manage-account" + ] + }, + "notBefore": 0, + "groups": [] + } ], + "keycloakVersion": "25.0.6", + "userManagedAccessAllowed": false, + "organizationsEnabled": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} diff --git a/integration-tests/oidc-mtls/src/test/java/io/quarkus/it/oidc/KeycloakTestResourceLifecycleManager.java b/integration-tests/oidc-mtls/src/test/java/io/quarkus/it/oidc/KeycloakTestResourceLifecycleManager.java new file mode 100644 index 00000000000000..fbbe85f5fdc5eb --- /dev/null +++ b/integration-tests/oidc-mtls/src/test/java/io/quarkus/it/oidc/KeycloakTestResourceLifecycleManager.java @@ -0,0 +1,49 @@ +package io.quarkus.it.oidc; + +import java.io.InputStream; +import java.net.URL; +import java.util.Map; + +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.util.JsonSerialization; + +import io.quarkus.test.common.DevServicesContext; +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import io.quarkus.test.keycloak.client.KeycloakTestClient; +import io.quarkus.test.keycloak.client.KeycloakTestClient.Tls; + +public class KeycloakTestResourceLifecycleManager implements QuarkusTestResourceLifecycleManager, + DevServicesContext.ContextAware { + + KeycloakTestClient client = new KeycloakTestClient( + new Tls("target/certificates/oidc-client-keystore.p12", + "target/certificates/oidc-client-truststore.p12")); + + @Override + public Map start() { + + client.createRealm(loadRealm()); + + return Map.of(); + } + + private static RealmRepresentation loadRealm() { + try { + URL realmPathUrl = Thread.currentThread().getContextClassLoader().getResource("quarkus-realm.json"); + try (InputStream is = realmPathUrl.openStream()) { + return JsonSerialization.readValue(is, RealmRepresentation.class); + } + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + @Override + public void setIntegrationTestContext(DevServicesContext context) { + client.setIntegrationTestContext(context); + } + + @Override + public void stop() { + } +} diff --git a/integration-tests/oidc-mtls/src/test/java/io/quarkus/it/oidc/OidcMtlsTest.java b/integration-tests/oidc-mtls/src/test/java/io/quarkus/it/oidc/OidcMtlsTest.java index 458c37b26b1ead..0529704d89240b 100644 --- a/integration-tests/oidc-mtls/src/test/java/io/quarkus/it/oidc/OidcMtlsTest.java +++ b/integration-tests/oidc-mtls/src/test/java/io/quarkus/it/oidc/OidcMtlsTest.java @@ -14,9 +14,11 @@ import io.quarkus.oidc.common.runtime.OidcConstants; import io.quarkus.runtime.util.ClassPathUtils; +import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.common.http.TestHTTPResource; import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.keycloak.client.KeycloakTestClient; +import io.quarkus.test.keycloak.client.KeycloakTestClient.Tls; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.core.net.KeyStoreOptions; @@ -25,31 +27,67 @@ import io.vertx.mutiny.ext.web.client.WebClient; @QuarkusTest +@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class) public class OidcMtlsTest { + KeycloakTestClient client = new KeycloakTestClient( + new Tls("target/certificates/oidc-client-keystore.p12", + "target/certificates/oidc-client-truststore.p12")); + @TestHTTPResource(tls = true) URL url; - KeycloakTestClient keycloakClient = new KeycloakTestClient(); + @Test + public void testMtlsJwt() throws Exception { + Vertx vertx = Vertx.vertx(); + try { + WebClientOptions options = createWebClientOptions(); + WebClient webClient = WebClient.create(new io.vertx.mutiny.core.Vertx(vertx), options); + + // HTTP 200 + HttpResponse resp = webClient.get("/service/mtls-jwt") + .putHeader("Authorization", + OidcConstants.BEARER_SCHEME + " " + getAccessToken("backend-service", null, "alice")) + .send().await() + .indefinitely(); + assertEquals(200, resp.statusCode()); + String name = resp.bodyAsString(); + assertEquals("Identities: CN=backend-service, alice;" + + " Client: backend-service;" + + " JWT cert thumbprint: true, introspection cert thumbprint: false", name); + + // HTTP 401, invalid token + resp = webClient.get("/service/mtls-jwt") + .putHeader("Authorization", OidcConstants.BEARER_SCHEME + " " + "123") + .send().await() + .indefinitely(); + assertEquals(401, resp.statusCode()); + } finally { + vertx.close(); + } + } @Test - public void testGetIdentityNames() throws Exception { + public void testMtlsIntrospection() throws Exception { Vertx vertx = Vertx.vertx(); try { WebClientOptions options = createWebClientOptions(); WebClient webClient = WebClient.create(new io.vertx.mutiny.core.Vertx(vertx), options); // HTTP 200 - HttpResponse resp = webClient.get("/service/name") - .putHeader("Authorization", OidcConstants.BEARER_SCHEME + " " + keycloakClient.getAccessToken("alice")) + HttpResponse resp = webClient.get("/service/mtls-introspection") + .putHeader("Authorization", + OidcConstants.BEARER_SCHEME + " " + getAccessToken("backend-service", null, "alice")) .send().await() .indefinitely(); assertEquals(200, resp.statusCode()); String name = resp.bodyAsString(); - assertEquals("Identities: CN=backend-service, alice", name); + assertEquals("Identities: CN=backend-service, alice;" + + " Client: backend-service;" + + " JWT cert thumbprint: false, introspection cert thumbprint: true", name); // HTTP 401, invalid token - resp = webClient.get("/service/name") + resp = webClient.get("/service/mtls-introspection") .putHeader("Authorization", OidcConstants.BEARER_SCHEME + " " + "123") .send().await() .indefinitely(); @@ -59,6 +97,42 @@ public void testGetIdentityNames() throws Exception { } } + @Test + public void testMtlsClientWithSecret() throws Exception { + Vertx vertx = Vertx.vertx(); + try { + WebClientOptions options = createWebClientOptions(); + WebClient webClient = WebClient.create(new io.vertx.mutiny.core.Vertx(vertx), options); + + String accessToken = getAccessToken("backend-client-with-secret", "secret", "alice"); + // HTTP 200 + HttpResponse resp = webClient.get("/service/mtls-client-with-secret") + .putHeader("Authorization", + OidcConstants.BEARER_SCHEME + " " + accessToken) + .send().await() + .indefinitely(); + assertEquals(200, resp.statusCode()); + String name = resp.bodyAsString(); + assertEquals("Identities: CN=backend-service, alice;" + + " Client: backend-client-with-secret;" + + " JWT cert thumbprint: false, introspection cert thumbprint: false", name); + + // HTTP 401, token is valid but it is not certificate bound + resp = webClient.get("/service/mtls-jwt") + .putHeader("Authorization", OidcConstants.BEARER_SCHEME + " " + accessToken) + .send().await() + .indefinitely(); + assertEquals(401, resp.statusCode()); + + } finally { + vertx.close(); + } + } + + private String getAccessToken(String clientName, String clientSecret, String userName) { + return client.getAccessToken(userName, userName, clientName, clientSecret); + } + private WebClientOptions createWebClientOptions() throws Exception { WebClientOptions webClientOptions = new WebClientOptions().setDefaultHost(url.getHost()) .setDefaultPort(url.getPort()).setSsl(true).setVerifyHost(false);