diff --git a/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx b/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx
index 56365a3f3b5..e9194da7eb6 100644
--- a/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx
+++ b/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx
@@ -303,7 +303,7 @@ function RealmSettingsGeneralTabForm({
diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/CredentialsOffer.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/CredentialsOffer.java
index 00298e1ab5a..7d785672f79 100644
--- a/services/src/main/java/org/keycloak/protocol/oid4vc/model/CredentialsOffer.java
+++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/CredentialsOffer.java
@@ -17,13 +17,18 @@
package org.keycloak.protocol.oid4vc.model;
+import java.beans.Transient;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
+import org.keycloak.common.util.KeycloakUriBuilder;
+
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
+import static org.keycloak.OID4VCConstants.WELL_KNOWN_OPENID_CREDENTIAL_ISSUER;
+
/**
* Represents a CredentialsOffer according to the OID4VCI Spec
* {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer}
@@ -52,6 +57,23 @@ public class CredentialsOffer {
return this;
}
+ @Transient
+ public String getIssuerMetadataUrl() {
+ var metadataUrl = KeycloakUriBuilder
+ .fromUri(credentialIssuer)
+ .path("/.well-known/" + WELL_KNOWN_OPENID_CREDENTIAL_ISSUER);
+ var idx = credentialIssuer.indexOf("/realms");
+ if (idx > 0) {
+ var baseUrl = credentialIssuer.substring(0, idx);
+ var realmPath = credentialIssuer.substring(idx);
+ metadataUrl = KeycloakUriBuilder
+ .fromUri(baseUrl)
+ .path("/.well-known/" + WELL_KNOWN_OPENID_CREDENTIAL_ISSUER)
+ .path(realmPath);
+ }
+ return metadataUrl.buildAsString();
+ }
+
public List getCredentialConfigurationIds() {
return credentialConfigurationIds;
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowTestBase.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowTestBase.java
index ee7dd839063..c0e49e769c8 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowTestBase.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowTestBase.java
@@ -96,7 +96,7 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
Oid4vcTestContext ctx = new Oid4vcTestContext();
// Get credential issuer metadata
- HttpGet getCredentialIssuer = new HttpGet(getRealmPath(TEST_REALM_NAME) + "/.well-known/openid-credential-issuer");
+ HttpGet getCredentialIssuer = new HttpGet(getRealmMetadataPath(TEST_REALM_NAME));
try (CloseableHttpResponse response = httpClient.execute(getCredentialIssuer)) {
assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
String s = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowWithPARTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowWithPARTest.java
index c2463ef94de..8c964359d30 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowWithPARTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowWithPARTest.java
@@ -99,7 +99,7 @@ public class OID4VCAuthorizationCodeFlowWithPARTest extends OID4VCIssuerEndpoint
Oid4vcTestContext ctx = new Oid4vcTestContext();
// Get credential issuer metadata
- HttpGet getCredentialIssuer = new HttpGet(getRealmPath(TEST_REALM_NAME) + "/.well-known/openid-credential-issuer");
+ HttpGet getCredentialIssuer = new HttpGet(getRealmMetadataPath(TEST_REALM_NAME));
try (CloseableHttpResponse response = httpClient.execute(getCredentialIssuer)) {
assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
String s = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationDetailsFlowTestBase.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationDetailsFlowTestBase.java
index 31a12eecaa9..0ecc5e2e837 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationDetailsFlowTestBase.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationDetailsFlowTestBase.java
@@ -121,7 +121,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue
ctx.credentialsOffer = JsonSerialization.readValue(s, CredentialsOffer.class);
}
- HttpGet getIssuerMetadata = new HttpGet(ctx.credentialsOffer.getCredentialIssuer() + "/.well-known/openid-credential-issuer");
+ HttpGet getIssuerMetadata = new HttpGet(ctx.credentialsOffer.getIssuerMetadataUrl());
try (CloseableHttpResponse response = httpClient.execute(getIssuerMetadata)) {
assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
String s = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerEndpointTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerEndpointTest.java
index 5dfa9349b8b..e70365ab2fc 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerEndpointTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerEndpointTest.java
@@ -68,7 +68,6 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.oid4vci.CredentialScopeModel;
import org.keycloak.protocol.oid4vc.issuance.OID4VCAuthorizationDetailsResponse;
import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint;
-import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProviderFactory;
import org.keycloak.protocol.oid4vc.issuance.TimeProvider;
import org.keycloak.protocol.oid4vc.issuance.credentialbuilder.CredentialBuilder;
import org.keycloak.protocol.oid4vc.issuance.credentialbuilder.JwtCredentialBuilder;
@@ -92,12 +91,10 @@ import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager;
-import org.keycloak.services.resources.RealmsResource;
import org.keycloak.testsuite.Assert;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.runonserver.RunOnServerException;
import org.keycloak.testsuite.util.AdminClientUtil;
-import org.keycloak.testsuite.util.oauth.OAuthClient;
import org.keycloak.util.JsonSerialization;
import com.fasterxml.jackson.core.type.TypeReference;
@@ -473,11 +470,8 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest {
String testCredentialConfigurationId = clientScope.getAttributes().get(CredentialScopeModel.CONFIGURATION_ID);
try (Client client = AdminClientUtil.createResteasyClient()) {
- UriBuilder builder = UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT);
- URI oid4vciDiscoveryUri = RealmsResource.wellKnownProviderUrl(builder)
- .build(TEST_REALM_NAME,
- OID4VCIssuerWellKnownProviderFactory.PROVIDER_ID);
- WebTarget oid4vciDiscoveryTarget = client.target(oid4vciDiscoveryUri);
+ String metadataUrl = getRealmMetadataPath(TEST_REALM_NAME);
+ WebTarget oid4vciDiscoveryTarget = client.target(metadataUrl);
// 1. Get authoriZation code without scope specified by wallet
// 2. Using the code to get accesstoken
@@ -528,7 +522,13 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest {
}
protected String getRealmPath(String realm) {
- return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/realms/" + realm;
+ return suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/" + realm;
+ }
+
+ protected String getRealmMetadataPath(String realm) {
+ var contextRoot = suiteContext.getAuthServerInfo().getContextRoot();
+ // [TODO] This should be contextRoot/.well-known/openid-credential-issuer/auth/realms/...
+ return contextRoot + "/auth/.well-known/openid-credential-issuer/realms/" + realm;
}
protected void requestCredential(String token,
@@ -558,7 +558,7 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest {
}
public CredentialIssuer getCredentialIssuerMetadata() {
- final String endpoint = getRealmPath(TEST_REALM_NAME) + "/.well-known/openid-credential-issuer";
+ final String endpoint = getRealmMetadataPath(TEST_REALM_NAME);
HttpGet getMetadataRequest = new HttpGet(endpoint);
try (CloseableHttpResponse metadataResponse = httpClient.execute(getMetadataRequest)) {
assertEquals(HttpStatus.SC_OK, metadataResponse.getStatusLine().getStatusCode());
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerWellKnownProviderTest.java
index b076159df8a..bab0b2f1f0b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerWellKnownProviderTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerWellKnownProviderTest.java
@@ -19,7 +19,6 @@ package org.keycloak.testsuite.oid4vc.issuance.signing;
import java.io.IOException;
import java.io.Serializable;
-import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
@@ -31,7 +30,6 @@ import java.util.Optional;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Response;
-import jakarta.ws.rs.core.UriBuilder;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.common.util.Time;
@@ -52,7 +50,6 @@ import org.keycloak.protocol.ProtocolMapper;
import org.keycloak.protocol.oid4vc.OID4VCLoginProtocolFactory;
import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint;
import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProvider;
-import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProviderFactory;
import org.keycloak.protocol.oid4vc.issuance.mappers.OID4VCMapper;
import org.keycloak.protocol.oid4vc.model.Claim;
import org.keycloak.protocol.oid4vc.model.ClaimDisplay;
@@ -68,7 +65,6 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ClientScopeRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
-import org.keycloak.services.resources.RealmsResource;
import org.keycloak.testsuite.arquillian.SuiteContext;
import org.keycloak.testsuite.client.KeycloakTestingClient;
import org.keycloak.testsuite.util.AdminClientUtil;
@@ -133,7 +129,7 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
@Test
public void testUnsignedMetadata() {
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
- String wellKnownUri = OAuthClient.AUTH_SERVER_ROOT + "/realms/" + TEST_REALM_NAME + "/.well-known/openid-credential-issuer";
+ String wellKnownUri = getRealmMetadataPath(TEST_REALM_NAME);
String expectedIssuer = getRealmPath(TEST_REALM_NAME);
// Configure realm for unsigned metadata
@@ -173,7 +169,7 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
@Test
public void testSignedMetadata() {
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
- String wellKnownUri = OAuthClient.AUTH_SERVER_ROOT + "/realms/" + TEST_REALM_NAME + "/.well-known/openid-credential-issuer";
+ String wellKnownUri = getRealmMetadataPath(TEST_REALM_NAME);
String expectedIssuer = getRealmPath(TEST_REALM_NAME);
// Configure realm for signed metadata
@@ -249,7 +245,7 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
@Test
public void testUnsignedMetadataWhenSignedDisabled() {
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
- String wellKnownUri = OAuthClient.AUTH_SERVER_ROOT + "/realms/" + TEST_REALM_NAME + "/.well-known/openid-credential-issuer";
+ String wellKnownUri = getRealmMetadataPath(TEST_REALM_NAME);
String expectedIssuer = getRealmPath(TEST_REALM_NAME);
// Disable signed metadata
@@ -279,7 +275,7 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
@Test
public void testSignedMetadataWithInvalidLifespan() {
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
- String wellKnownUri = OAuthClient.AUTH_SERVER_ROOT + "/realms/" + TEST_REALM_NAME + "/.well-known/openid-credential-issuer";
+ String wellKnownUri = getRealmMetadataPath(TEST_REALM_NAME);
String expectedIssuer = getRealmPath(TEST_REALM_NAME);
// Configure invalid lifespan
@@ -309,7 +305,7 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
@Test
public void testSignedMetadataWithInvalidAlgorithm() {
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
- String wellKnownUri = OAuthClient.AUTH_SERVER_ROOT + "/realms/" + TEST_REALM_NAME + "/.well-known/openid-credential-issuer";
+ String wellKnownUri = getRealmMetadataPath(TEST_REALM_NAME);
String expectedIssuer = getRealmPath(TEST_REALM_NAME);
// Configure invalid algorithm
@@ -456,10 +452,8 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
@Test
public void testIssuerMetadataIncludesEncryptionSupport() throws IOException {
try (Client client = AdminClientUtil.createResteasyClient()) {
- UriBuilder builder = UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT);
- URI oid4vciDiscoveryUri = RealmsResource.wellKnownProviderUrl(builder)
- .build(TEST_REALM_NAME, OID4VCIssuerWellKnownProviderFactory.PROVIDER_ID);
- WebTarget oid4vciDiscoveryTarget = client.target(oid4vciDiscoveryUri);
+ String wellKnownUri = getRealmMetadataPath(TEST_REALM_NAME);
+ WebTarget oid4vciDiscoveryTarget = client.target(wellKnownUri);
try (Response discoveryResponse = oid4vciDiscoveryTarget.request().get()) {
CredentialIssuer oid4vciIssuerConfig = JsonSerialization.readValue(
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCJWTIssuerEndpointTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCJWTIssuerEndpointTest.java
index 6b38a41cb8a..f3a07c992d5 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCJWTIssuerEndpointTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCJWTIssuerEndpointTest.java
@@ -451,7 +451,7 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest {
CredentialsOffer credentialsOffer = JsonSerialization.readValue(s, CredentialsOffer.class);
// 3. Get the issuer metadata
- HttpGet getIssuerMetadata = new HttpGet(credentialsOffer.getCredentialIssuer() + "/.well-known/openid-credential-issuer");
+ HttpGet getIssuerMetadata = new HttpGet(credentialsOffer.getIssuerMetadataUrl());
CloseableHttpResponse issuerMetadataResponse = httpClient.execute(getIssuerMetadata);
assertEquals(HttpStatus.SC_OK, issuerMetadataResponse.getStatusLine().getStatusCode());
s = IOUtils.toString(issuerMetadataResponse.getEntity().getContent(), StandardCharsets.UTF_8);
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCSdJwtIssuingEndpointTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCSdJwtIssuingEndpointTest.java
index d8e86f2db5a..119dbc0b7b9 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCSdJwtIssuingEndpointTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCSdJwtIssuingEndpointTest.java
@@ -323,7 +323,7 @@ public class OID4VCSdJwtIssuingEndpointTest extends OID4VCIssuerEndpointTest {
CredentialsOffer credentialsOffer = JsonSerialization.readValue(s, CredentialsOffer.class);
// 3. Get the issuer metadata
- HttpGet getIssuerMetadata = new HttpGet(credentialsOffer.getCredentialIssuer() + "/.well-known/openid-credential-issuer");
+ HttpGet getIssuerMetadata = new HttpGet(credentialsOffer.getIssuerMetadataUrl());
CloseableHttpResponse issuerMetadataResponse = httpClient.execute(getIssuerMetadata);
assertEquals(HttpStatus.SC_OK, issuerMetadataResponse.getStatusLine().getStatusCode());
s = IOUtils.toString(issuerMetadataResponse.getEntity().getContent(), StandardCharsets.UTF_8);