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 5c537f0a14514..41481e827d60f 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 @@ -112,7 +112,7 @@ public class KeycloakDevServicesProcessor { private static final String KEYCLOAK_QUARKUS_ADMIN_PROP = "KC_BOOTSTRAP_ADMIN_USERNAME"; private static final String KEYCLOAK_QUARKUS_ADMIN_PASSWORD_PROP = "KC_BOOTSTRAP_ADMIN_PASSWORD"; private static final String KEYCLOAK_QUARKUS_START_CMD = "start --http-enabled=true --hostname-strict=false " - + "--spi-user-profile-declarative-user-profile-config-file=/opt/keycloak/upconfig.json"; + + "--features=preview --spi-user-profile-declarative-user-profile-config-file=/opt/keycloak/upconfig.json"; private static final String JAVA_OPTS = "JAVA_OPTS"; private static final String OIDC_USERS = "oidc.users"; diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java index 224ad6af5ab78..1973cae9c2419 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java @@ -782,6 +782,10 @@ public static String base64UrlDecode(String encodedContent) { return new String(Base64.getUrlDecoder().decode(encodedContent), StandardCharsets.UTF_8); } + public static String base64UrlEncode(byte[] bytes) { + return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes); + } + public static JsonObject decodeAsJsonObject(String encodedContent) { try { return new JsonObject(base64UrlDecode(encodedContent)); 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 73b6dde2e7225..7ce6c5657830c 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 @@ -46,6 +46,7 @@ public final class OidcConstants { public static final String CLIENT_SECRET = "client_secret"; public static final String BEARER_SCHEME = "Bearer"; + public static final String DPOP_SCHEME = "DPoP"; public static final String BASIC_SCHEME = "Basic"; public static final String AUTHORIZATION_CODE = "authorization_code"; @@ -89,4 +90,9 @@ public final class OidcConstants { public static final String CONFIRMATION_CLAIM = "cnf"; public static final String X509_SHA256_THUMBPRINT = "x5t#S256"; + public static final String DPOP_JWK_SHA256_THUMBPRINT = "jkt"; + public static final String DPOP_JWK_HEADER = "jwk"; + public static final String DPOP_ACCESS_TOKEN_THUMBPRINT = "ath"; + public static final String DPOP_HTTP_METHOD = "htm"; + public static final String DPOP_HTTP_REQUEST_URI = "htu"; } 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 8fabfca0601ba..52f660fb20807 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 @@ -14,12 +14,14 @@ import io.netty.handler.codec.http.HttpResponseStatus; import io.quarkus.oidc.AccessTokenCredential; import io.quarkus.oidc.OidcTenantConfig; +import io.quarkus.oidc.common.runtime.OidcCommonUtils; 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; import io.smallrye.mutiny.Uni; +import io.vertx.core.json.JsonObject; import io.vertx.ext.web.RoutingContext; public class BearerAuthenticationMechanism extends AbstractOidcAuthenticationMechanism { @@ -33,6 +35,7 @@ public Uni authenticate(RoutingContext context, if (token != null) { try { setCertificateThumbprint(context, oidcTenantConfig); + setDPopProof(context, oidcTenantConfig); } catch (AuthenticationFailedException ex) { return Uni.createFrom().failure(ex); } @@ -54,6 +57,52 @@ private static void setCertificateThumbprint(RoutingContext context, OidcTenantC } } + private static void setDPopProof(RoutingContext context, OidcTenantConfig oidcTenantConfig) { + if (OidcConstants.DPOP_SCHEME.equals(oidcTenantConfig.token().authorizationScheme())) { + String proof = context.request().getHeader(OidcConstants.DPOP_SCHEME); + if (proof == null) { + LOG.warn("DPOP proof header must be present to verify the DPOP access token binding"); + throw new AuthenticationFailedException(); + } + // Initial proof check: + JsonObject proofJwtHeaders = OidcUtils.decodeJwtHeaders(proof); + JsonObject proofJwtClaims = OidcCommonUtils.decodeJwtContent(proof); + + // Check HTTP method and request URI + String proofHttpMethod = proofJwtClaims.getString(OidcConstants.DPOP_HTTP_METHOD); + if (proofHttpMethod == null) { + LOG.warn("DPOP proof HTTP method claim is missing"); + throw new AuthenticationFailedException(); + } + + if (!context.request().method().name().equals(proofHttpMethod)) { + LOG.warn("DPOP proof HTTP method claim does not match the request HTTP method"); + throw new AuthenticationFailedException(); + } + + // Check HTTP request URI + String proofHttpRequestUri = proofJwtClaims.getString(OidcConstants.DPOP_HTTP_REQUEST_URI); + if (proofHttpRequestUri == null) { + LOG.warn("DPOP proof HTTP request uri claim is missing"); + throw new AuthenticationFailedException(); + } + + String httpRequestUri = context.request().absoluteURI(); + int queryIndex = httpRequestUri.indexOf("?"); + if (queryIndex > 0) { + httpRequestUri = httpRequestUri.substring(0, queryIndex); + } + if (!httpRequestUri.equals(proofHttpRequestUri)) { + LOG.warn("DPOP proof HTTP request uri claim does not match the request HTTP uri"); + throw new AuthenticationFailedException(); + } + + context.put(OidcUtils.DPOP_PROOF, proof); + context.put(OidcUtils.DPOP_PROOF_JWT_HEADERS, proofJwtHeaders); + context.put(OidcUtils.DPOP_PROOF_JWT_CLAIMS, proofJwtClaims); + } + } + private static Certificate getCertificate(RoutingContext context) { try { return context.request().sslSession().getPeerCertificates()[0]; 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 434ed37149822..3bb408de69c0f 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 @@ -3,6 +3,7 @@ import static io.quarkus.oidc.runtime.OidcUtils.validateAndCreateIdentity; import static io.quarkus.vertx.http.runtime.security.HttpSecurityUtils.getRoutingContextAttribute; +import java.security.NoSuchAlgorithmException; import java.security.Principal; import java.util.Map; import java.util.Set; @@ -14,6 +15,8 @@ import org.eclipse.microprofile.jwt.Claims; import org.jboss.logging.Logger; +import org.jose4j.jwk.PublicJsonWebKey; +import org.jose4j.lang.JoseException; import org.jose4j.lang.UnresolvableKeyException; import io.quarkus.oidc.AccessTokenCredential; @@ -201,33 +204,100 @@ private Uni verifyPrimaryTokenUni(Map r final boolean idToken = isIdToken(request); Uni result = verifyTokenUni(requestData, resolvedContext, request.getToken(), idToken, false, userInfo); - if (!idToken && resolvedContext.oidcConfig().token().binding().certificate()) { - return result.onItem().transform(new Function() { + if (!idToken) { + if (resolvedContext.oidcConfig().token().binding().certificate()) { + result = 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(); + @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; } - if (!clientCertificateThumbprint.equals(tokenCertificateThumbprint)) { - LOG.warn("Client certificate thumbprint does not match the token certificate thumbprint"); - throw new AuthenticationFailedException(); + + }); + } + + if (requestData.containsKey(OidcUtils.DPOP_PROOF_JWT_HEADERS)) { + result = result.onItem().transform(new Function() { + + @Override + public TokenVerificationResult apply(TokenVerificationResult t) { + + String dpopJwkThumbprint = getDpopJwkThumbprint(requestData, t); + if (dpopJwkThumbprint == null) { + LOG.warn( + "DPoP access token does not contain a confirmation 'cnf' claim with the JWK certificate thumbprint"); + throw new AuthenticationFailedException(); + } + + JsonObject proofHeaders = (JsonObject) requestData.get(OidcUtils.DPOP_PROOF_JWT_HEADERS); + + JsonObject jwkProof = proofHeaders.getJsonObject(OidcConstants.DPOP_JWK_HEADER); + if (jwkProof == null) { + LOG.warn("DPoP proof jwk header is missing"); + throw new AuthenticationFailedException(); + } + + String jwkProofThumbprint = null; + try { + byte[] jwkProofDigest = PublicJsonWebKey.Factory.newPublicJwk(jwkProof.getMap()) + .calculateThumbprint("SHA-256"); + jwkProofThumbprint = OidcCommonUtils.base64UrlEncode(jwkProofDigest); + } catch (JoseException ex) { + LOG.warn("JWK thumbprint can not be calculated"); + throw new AuthenticationFailedException(ex); + } + + if (!dpopJwkThumbprint.equals(jwkProofThumbprint)) { + LOG.warn("DPoP access token JWK thumbprint does not match the DPoP proof JWK thumbprint"); + throw new AuthenticationFailedException(); + } + + JsonObject proofClaims = (JsonObject) requestData.get(OidcUtils.DPOP_PROOF_JWT_CLAIMS); + + // Calculate the access token thumprint and compare with the `ath` claim + + String accessTokenProof = proofClaims.getString(OidcConstants.DPOP_ACCESS_TOKEN_THUMBPRINT); + if (accessTokenProof == null) { + LOG.warn("DPoP proof access token hash is missing"); + throw new AuthenticationFailedException(); + } + + String accessTokenHash = null; + try { + accessTokenHash = OidcCommonUtils.base64UrlEncode( + OidcUtils.getSha256Digest(request.getToken().getToken())); + } catch (NoSuchAlgorithmException ex) { + // SHA256 is always supported + } + + if (!accessTokenProof.equals(accessTokenHash)) { + LOG.warn("DPoP access token hash does not match the DPoP proof access token hash"); + throw new AuthenticationFailedException(); + } + + return t; } - return t; - } - }); - } else { - return result; + }); + } } + + return result; } } @@ -243,6 +313,19 @@ private static String getTokenCertThumbprint(Map requestData, To return thumbprint; } + private static String getDpopJwkThumbprint(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.DPOP_JWK_SHA256_THUMBPRINT); + if (thumbprint != null) { + requestData.put( + (t.introspectionResult == null ? OidcUtils.DPOP_JWT_THUMBPRINT : OidcUtils.DPOP_INTROSPECTION_THUMBPRINT), + true); + } + return thumbprint; + } + private Uni getUserInfoAndCreateIdentity(Uni tokenUni, Map requestData, TokenAuthenticationRequest request, diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClientImpl.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClientImpl.java index 56d53f00498ea..baf102e2f74cb 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClientImpl.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/OidcProviderClientImpl.java @@ -340,7 +340,8 @@ private UniOnItem> getHttpResponse(OidcRequestContextProper } } - LOG.debugf("Get token on: %s params: %s headers: %s", metadata.getTokenUri(), formBody, request.headers()); + LOG.debugf("%s token: %s params: %s headers: %s", (introspect ? "Introspect" : "Get"), metadata.getTokenUri(), formBody, + request.headers()); // Retry up to three times with a one-second delay between the retries if the connection is closed. OidcEndpoint.Type endpoint = introspect ? OidcEndpoint.Type.INTROSPECTION : OidcEndpoint.Type.TOKEN; 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 878f0295a76bf..f66c2d93840ce 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 @@ -5,6 +5,8 @@ import static io.quarkus.oidc.common.runtime.OidcConstants.TOKEN_SCOPE; import static io.quarkus.vertx.http.runtime.security.HttpSecurityUtils.getRoutingContextAttribute; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.security.Key; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -97,6 +99,12 @@ public final class OidcUtils { 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"; + public static final String DPOP_JWT_THUMBPRINT = "dpop_jwt_thumbprint"; + public static final String DPOP_INTROSPECTION_THUMBPRINT = "dpop_introspection_thumbprint"; + public static final String DPOP_PROOF = "dpop_proof"; + public static final String DPOP_PROOF_JWT_HEADERS = "dpop_proof_jwt_headers"; + public static final String DPOP_PROOF_JWT_CLAIMS = "dpop_proof_jwt_claims"; + private static final String APPLICATION_JWT = "application/jwt"; // Browsers enforce that the total Set-Cookie expression such as @@ -562,6 +570,14 @@ static OidcTenantConfig resolveProviderConfig(OidcTenantConfig oidcTenantConfig) } + public static byte[] getSha256Digest(String value) throws NoSuchAlgorithmException { + return getSha256Digest(value, StandardCharsets.UTF_8); + } + + public static byte[] getSha256Digest(String value, Charset charset) throws NoSuchAlgorithmException { + return getSha256Digest(value.getBytes(charset)); + } + public static byte[] getSha256Digest(byte[] value) throws NoSuchAlgorithmException { MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); sha256.update(value); diff --git a/integration-tests/oidc-dpop/pom.xml b/integration-tests/oidc-dpop/pom.xml new file mode 100644 index 0000000000000..59b0f53430fd2 --- /dev/null +++ b/integration-tests/oidc-dpop/pom.xml @@ -0,0 +1,139 @@ + + + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-integration-test-oidc-dpop + Quarkus - Integration Tests - OpenID Connect DPoP + Module that contains OpenID Connect DPoP tests + + + + io.quarkus + quarkus-oidc + + + io.quarkus + quarkus-oidc-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-rest + + + io.quarkus + quarkus-rest-deployment + ${project.version} + pom + test + + + * + * + + + + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + org.htmlunit + htmlunit + test + + + + + + + maven-surefire-plugin + + true + + + + maven-failsafe-plugin + + true + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + test-keycloak + + + test-containers + + + + + + maven-surefire-plugin + + false + + + + maven-failsafe-plugin + + false + + ${keycloak.url} + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + diff --git a/integration-tests/oidc-dpop/src/main/java/io/quarkus/it/keycloak/FrontendResource.java b/integration-tests/oidc-dpop/src/main/java/io/quarkus/it/keycloak/FrontendResource.java new file mode 100644 index 0000000000000..f1a8ffe04c3fb --- /dev/null +++ b/integration-tests/oidc-dpop/src/main/java/io/quarkus/it/keycloak/FrontendResource.java @@ -0,0 +1,162 @@ +package io.quarkus.it.keycloak; + +import java.net.URI; +import java.security.KeyPair; +import java.util.UUID; + +import jakarta.annotation.PreDestroy; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.NewCookie; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.Response.ResponseBuilder; +import jakarta.ws.rs.core.UriInfo; + +import io.quarkus.oidc.OidcConfigurationMetadata; +import io.quarkus.oidc.common.runtime.OidcCommonUtils; +import io.quarkus.oidc.common.runtime.OidcConstants; +import io.quarkus.oidc.runtime.OidcUtils; +import io.quarkus.oidc.runtime.TenantConfigBean; +import io.smallrye.jwt.build.Jwt; +import io.smallrye.jwt.util.KeyUtils; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.json.JsonObject; +import io.vertx.mutiny.core.MultiMap; +import io.vertx.mutiny.core.Vertx; +import io.vertx.mutiny.core.buffer.Buffer; +import io.vertx.mutiny.ext.web.client.HttpRequest; +import io.vertx.mutiny.ext.web.client.HttpResponse; +import io.vertx.mutiny.ext.web.client.WebClient; + +@Path("/single-page-app") +public class FrontendResource { + + @Inject + TenantConfigBean oidcTenants; + + @Context + UriInfo ui; + + private final WebClient client; + + @Inject + public FrontendResource(Vertx vertx) { + this.client = WebClient.create(vertx); + } + + @GET + @Path("login-jwt") + public Response loginJwt() { + return redirect("dpop-jwt", "callback-jwt"); + } + + @GET + @Path("callback-jwt") + public Response callbackJwt(@QueryParam("code") String code) throws Exception { + return callProtectedEndpoint(code, "dpop-jwt", "callback-jwt", "GET", "dpop-jwt", "dpop-jwt"); + } + + @GET + @Path("login-jwt-wrong-dpop-http-method") + public Response loginJwtWrongDpopHttpMethod() { + return redirect("dpop-jwt", "callback-jwt-wrong-dpop-http-method"); + } + + @GET + @Path("callback-jwt-wrong-dpop-http-method") + public Response callbackWrongDpopHttpMethod(@QueryParam("code") String code) throws Exception { + return callProtectedEndpoint(code, "dpop-jwt", "callback-jwt", "POST", "dpop-jwt", "dpop-jwt"); + } + + private Response callProtectedEndpoint(String code, String tenantId, String redirectPath, String dPopHttpMethod, + String dPopEndpointPath, String quarkusEndpointPath) + throws Exception { + String redirectUriParam = ui.getBaseUriBuilder().path("single-page-app").path(redirectPath).build().toString(); + + MultiMap grantParams = MultiMap.caseInsensitiveMultiMap(); + grantParams.add(OidcConstants.CLIENT_ID, "backend-service"); + grantParams.add(OidcConstants.GRANT_TYPE, OidcConstants.AUTHORIZATION_CODE); + grantParams.add(OidcConstants.CODE_FLOW_CODE, code); + grantParams.add(OidcConstants.CODE_FLOW_REDIRECT_URI, redirectUriParam); + + Buffer encoded = OidcCommonUtils.encodeForm(grantParams); + HttpRequest requestToKeycloak = client.postAbs(getConfigMetadata(tenantId).getTokenUri()); + requestToKeycloak.putHeader("Content-Type", HttpHeaders.APPLICATION_X_WWW_FORM_URLENCODED.toString()); + + KeyPair keyPair = KeyUtils.generateKeyPair(2048); + requestToKeycloak.putHeader("DPoP", createDPopProofForKeycloak(keyPair, tenantId)); + + JsonObject grantResponse = requestToKeycloak.sendBufferAndAwait(encoded).bodyAsJsonObject(); + String accessToken = grantResponse.getString("access_token"); + + String requestPath = ui.getBaseUriBuilder().path("service").path(quarkusEndpointPath).build().toString(); + HttpRequest requestToQuarkus = client.getAbs(requestPath); + requestToQuarkus.putHeader("Accept", "text/plain"); + requestToQuarkus.putHeader("DPoP", createDPopProofForQuarkus(keyPair, accessToken, dPopHttpMethod, dPopEndpointPath)); + requestToQuarkus.putHeader("Authorization", "DPoP " + accessToken); + HttpResponse response = requestToQuarkus.sendAndAwait(); + ResponseBuilder builder = Response.status(response.statusCode()); + if (response.statusCode() == 200) { + builder.entity(response.bodyAsString()); + } + return builder.build(); + + } + + private Response redirect(String tenantId, String callbackPath) { + StringBuilder codeFlowParams = new StringBuilder(); + + // response_type + codeFlowParams.append(OidcConstants.CODE_FLOW_RESPONSE_TYPE).append("=").append(OidcConstants.CODE_FLOW_CODE); + // client_id + codeFlowParams.append("&").append(OidcConstants.CLIENT_ID).append("=").append("backend-service"); + // scope + codeFlowParams.append("&").append(OidcConstants.TOKEN_SCOPE).append("=").append("openid"); + // redirect_uri + String redirectUriParam = ui.getBaseUriBuilder().path("single-page-app/" + callbackPath).build().toString(); + codeFlowParams.append("&").append(OidcConstants.CODE_FLOW_REDIRECT_URI).append("=") + .append(OidcCommonUtils.urlEncode(redirectUriParam)); + // state + String state = UUID.randomUUID().toString(); + codeFlowParams.append("&").append(OidcConstants.CODE_FLOW_STATE).append("=").append(state); + return Response + .seeOther(URI.create(getConfigMetadata(tenantId).getAuthorizationUri() + "?" + codeFlowParams.toString())) + .cookie(new NewCookie("state", state)) + .build(); + } + + @PreDestroy + void close() { + client.close(); + } + + private String createDPopProofForKeycloak(KeyPair keyPair, String tenantId) throws Exception { + + return Jwt.claim("htm", "POST") + .claim("htu", getConfigMetadata(tenantId).getTokenUri()) + .jws() + .header("typ", "dpop+jwt") + .jwk(keyPair.getPublic()) + .sign(keyPair.getPrivate()); + } + + private String createDPopProofForQuarkus(KeyPair keyPair, String accessToken, String dPopHttpMethod, + String dPopEndpointPath) throws Exception { + + return Jwt.claim("htm", dPopHttpMethod) + .claim("htu", dPopEndpointPath) + .claim("ath", OidcCommonUtils.base64UrlEncode( + OidcUtils.getSha256Digest(accessToken))) + .jws() + .header("typ", "dpop+jwt") + .jwk(keyPair.getPublic()) + .sign(keyPair.getPrivate()); + } + + private OidcConfigurationMetadata getConfigMetadata(String tenantId) { + return oidcTenants.getStaticTenant(tenantId).getOidcMetadata(); + } +} diff --git a/integration-tests/oidc-dpop/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java b/integration-tests/oidc-dpop/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java new file mode 100644 index 0000000000000..808f34b706acf --- /dev/null +++ b/integration-tests/oidc-dpop/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java @@ -0,0 +1,41 @@ +package io.quarkus.it.keycloak; + +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; + +import org.eclipse.microprofile.jwt.JsonWebToken; + +import io.quarkus.oidc.runtime.OidcUtils; +import io.quarkus.security.Authenticated; +import io.vertx.ext.web.RoutingContext; + +@Path("/service") +@Authenticated +public class ProtectedResource { + + @Inject + JsonWebToken principal; + + @Inject + RoutingContext routingContext; + + @GET + @Produces("text/plain") + @Path("dpop-jwt") + public String hello() { + return "Hello, " + principal.getName() + "; " + + "JWK thumbprint in JWT: " + isJwtTokenThumbprintAvailable() + ", " + + "JWK thumbprint in introspection: " + isIntrospectionThumbprintAvailable(); + } + + private boolean isJwtTokenThumbprintAvailable() { + return Boolean.TRUE.equals(routingContext.get(OidcUtils.DPOP_JWT_THUMBPRINT)); + } + + private boolean isIntrospectionThumbprintAvailable() { + + return Boolean.TRUE.equals(routingContext.get(OidcUtils.DPOP_INTROSPECTION_THUMBPRINT)); + } +} diff --git a/integration-tests/oidc-dpop/src/main/resources/application.properties b/integration-tests/oidc-dpop/src/main/resources/application.properties new file mode 100644 index 0000000000000..301c56adeece4 --- /dev/null +++ b/integration-tests/oidc-dpop/src/main/resources/application.properties @@ -0,0 +1,9 @@ +# Disable default tenant +quarkus.oidc.tenant-enabled=false +quarkus.keycloak.devservices.start-with-disabled-tenant=true +quarkus.keycloak.devservices.realm-path=quarkus-realm.json + +quarkus.oidc.dpop-jwt.auth-server-url=${quarkus.oidc.auth-server-url} +quarkus.oidc.dpop-jwt.token.authorization-scheme=DPoP + +quarkus.log.category."org.htmlunit".level=ERROR diff --git a/integration-tests/oidc-dpop/src/main/resources/quarkus-realm.json b/integration-tests/oidc-dpop/src/main/resources/quarkus-realm.json new file mode 100644 index 0000000000000..5b1a86aad740d --- /dev/null +++ b/integration-tests/oidc-dpop/src/main/resources/quarkus-realm.json @@ -0,0 +1,2366 @@ +{ + "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, + "bruteForceStrategy": "MULTIPLE", + "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": "6607d6c4-60c7-412e-badc-8e86e1925fb0", + "name": "default-roles-quarkus", + "description": "${role_default-roles}", + "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": "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": {} + } + ], + "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": "c84d374e-ee49-47e0-b65a-21519e7f3a99", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "35b5a50f-a32a-4bd1-b4b3-50f0ade135c7", + "attributes": {} + }, + { + "id": "fee1f70e-3a5d-4598-b5a3-6ff9ee208836", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "35b5a50f-a32a-4bd1-b4b3-50f0ade135c7", + "attributes": {} + }, + { + "id": "97a0dbe6-618d-4f8b-997a-9802467b4350", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "35b5a50f-a32a-4bd1-b4b3-50f0ade135c7", + "attributes": {} + }, + { + "id": "9a2ff099-0b69-444c-9c4d-801c5eda630c", + "name": "view-groups", + "description": "${role_view-groups}", + "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": "498c752e-d7eb-442a-bc48-f0013665179d", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "35b5a50f-a32a-4bd1-b4b3-50f0ade135c7", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRole": { + "id": "6607d6c4-60c7-412e-badc-8e86e1925fb0", + "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", + "RS256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "webAuthnPolicyPasswordlessExtraOrigins": [], + "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": { + "realm_client": "false", + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "d8adbd75-4d35-4951-84a6-036359747daf", + "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": { + "realm_client": "false", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "79f82b8d-e4e0-4c6a-bf4d-5d2118393461", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "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": { + "realm_client": "false", + "client.use.lightweight.access.token.enabled": "true", + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": 0, + "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-secret", + "redirectUris": [ + "*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "realm_client": "false", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "+", + "oauth2.device.authorization.grant.enabled": "false", + "display.on.consent.screen": "false", + "backchannel.logout.revoke.offline.tokens": "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", + "acr", + "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": { + "realm_client": "true", + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "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": { + "realm_client": "true", + "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": { + "realm_client": "false", + "client.use.lightweight.access.token.enabled": "true", + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "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", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "0d8dbb0f-0d91-40bf-a263-2706457aebe8", + "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": "247c235a-181b-43e8-ad41-9346ef74845c", + "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": "6e788b2f-e327-472d-97a0-5ecd218b773c", + "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": "f4b5b2bd-b926-458f-9067-a28f2d0f67c6", + "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": "6357c42c-8349-4555-83d0-777cacdf3455", + "name": "sub", + "protocol": "openid-connect", + "protocolMapper": "oidc-sub-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.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" + } + } + ] + } + ], + "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": [ + "saml-user-attribute-mapper", + "oidc-full-name-mapper", + "saml-user-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-usermodel-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-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": [ + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", + "saml-user-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-address-mapper", + "saml-role-list-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-full-name-mapper" + ] + } + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "083eacd8-ab82-47fe-8743-55121911bd5e", + "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": "b9b879cf-66e2-48df-adb5-3b61e1f39bc7", + "name": "hmac-generated-hs512", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS512" + ] + } + }, + { + "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": "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", + "createdTimestamp" : 1554245880023, + "username" : "service-account-backend-service", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "email" : "service-account-backend-service@placeholder.org", + "serviceAccountClientId" : "backend-service", + "credentials" : [ ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "offline_access" ], + "clientRoles" : { + "backend-service" : [ "uma_protection" ], + "account" : [ "view-profile", "manage-account" ] + }, + "notBefore" : 0, + "groups" : [ ] + } ], + "keycloakVersion": "26.0.7", + "userManagedAccessAllowed": false, + "organizationsEnabled": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} diff --git a/integration-tests/oidc-dpop/src/test/java/io/quarkus/it/keycloak/OidcDPopInGraalITCase.java b/integration-tests/oidc-dpop/src/test/java/io/quarkus/it/keycloak/OidcDPopInGraalITCase.java new file mode 100644 index 0000000000000..51a9fa892627a --- /dev/null +++ b/integration-tests/oidc-dpop/src/test/java/io/quarkus/it/keycloak/OidcDPopInGraalITCase.java @@ -0,0 +1,7 @@ +package io.quarkus.it.keycloak; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class OidcDPopInGraalITCase extends OidcDPopTest { +} diff --git a/integration-tests/oidc-dpop/src/test/java/io/quarkus/it/keycloak/OidcDPopTest.java b/integration-tests/oidc-dpop/src/test/java/io/quarkus/it/keycloak/OidcDPopTest.java new file mode 100644 index 0000000000000..d1a710e3d82df --- /dev/null +++ b/integration-tests/oidc-dpop/src/test/java/io/quarkus/it/keycloak/OidcDPopTest.java @@ -0,0 +1,46 @@ +package io.quarkus.it.keycloak; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.htmlunit.SilentCssErrorHandler; +import org.htmlunit.TextPage; +import org.htmlunit.WebClient; +import org.htmlunit.html.HtmlForm; +import org.htmlunit.html.HtmlPage; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class OidcDPopTest { + + @Test + public void testDPoPConfirmationInJwtToken() throws Exception { + try (final WebClient webClient = createWebClient()) { + HtmlPage page = webClient.getPage("http://localhost:8081/single-page-app/login-jwt"); + + assertEquals("Sign in to quarkus", page.getTitleText()); + + HtmlForm loginForm = page.getForms().get(0); + + loginForm.getInputByName("username").setValueAttribute("alice"); + loginForm.getInputByName("password").setValueAttribute("alice"); + + TextPage textPage = loginForm.getButtonByName("login").click(); + + assertEquals("Hello, alice; JWK thumbprint in JWT: true, JWK thumbprint in introspection: false", + textPage.getContent()); + + webClient.getPage("http://localhost:8081/single-page-app/login-jwt"); + + webClient.getCookieManager().clearCookies(); + } + } + + private WebClient createWebClient() { + WebClient webClient = new WebClient(); + webClient.setCssErrorHandler(new SilentCssErrorHandler()); + return webClient; + } + +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 55f67dc439690..36f0b3414318d 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -259,6 +259,7 @@ oidc-client-wiremock oidc-dev-services oidc-mtls + oidc-dpop oidc-token-propagation oidc-token-propagation-reactive openapi