From 2af7c843af70fc07fe3845df51edb2ac20156604 Mon Sep 17 00:00:00 2001 From: Rathan Naik <30756840+Rathan-Naik@users.noreply.github.com> Date: Sat, 3 Jan 2026 22:28:29 +0530 Subject: [PATCH] Fix organization invitation redirect to respect account client base URL When an organization's redirect URL is left empty, Keycloak currently defaults to the account console URL, ignoring the account client's configured Home URL (base URL). This fix checks the account client's base URL before falling back to the default account console URL. Changes: - Added resolveAccountClientBaseUrl() helper method in OrganizationInvitationResource - Added setBaseUrl() method to ClientAttributeUpdater test utility - Added integration tests for the new behavior Closes #45052 Signed-off-by: Rathan Naik <30756840+Rathan-Naik@users.noreply.github.com> --- .../OrganizationInvitationResource.java | 17 ++++++- .../updaters/ClientAttributeUpdater.java | 5 ++ .../admin/OrganizationInvitationLinkTest.java | 48 +++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/services/src/main/java/org/keycloak/organization/admin/resource/OrganizationInvitationResource.java b/services/src/main/java/org/keycloak/organization/admin/resource/OrganizationInvitationResource.java index 36f59b0ad7d..45b99239e29 100644 --- a/services/src/main/java/org/keycloak/organization/admin/resource/OrganizationInvitationResource.java +++ b/services/src/main/java/org/keycloak/organization/admin/resource/OrganizationInvitationResource.java @@ -39,6 +39,7 @@ import org.keycloak.email.EmailException; import org.keycloak.email.EmailTemplateProvider; import org.keycloak.events.admin.OperationType; import org.keycloak.events.admin.ResourceType; +import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.OrganizationInvitationModel; @@ -58,6 +59,7 @@ import org.keycloak.services.Urls; import org.keycloak.services.resources.KeycloakOpenAPI; import org.keycloak.services.resources.LoginActionsService; import org.keycloak.services.resources.admin.AdminEventBuilder; +import org.keycloak.services.util.ResolveRelative; import org.keycloak.services.validation.Validation; import org.keycloak.storage.adapter.InMemoryUserAdapter; import org.keycloak.utils.StringUtil; @@ -203,7 +205,7 @@ public class OrganizationInvitationResource { token.id(invitation.getId()); if (organization.getRedirectUrl() == null || organization.getRedirectUrl().isBlank()) { - token.setRedirectUri(Urls.accountBase(session.getContext().getUri().getBaseUri()).path("/").build(realm.getName()).toString()); + token.setRedirectUri(resolveAccountClientBaseUrl()); } else { token.setRedirectUri(organization.getRedirectUrl()); } @@ -211,6 +213,19 @@ public class OrganizationInvitationResource { return token.serialize(session, realm, session.getContext().getUri()); } + private String resolveAccountClientBaseUrl() { + ClientModel accountClient = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID); + + if (accountClient != null) { + String baseUrl = accountClient.getBaseUrl(); + if (baseUrl != null && !baseUrl.isBlank()) { + return ResolveRelative.resolveRelativeUri(session, accountClient.getRootUrl(), baseUrl); + } + } + + return Urls.accountBase(session.getContext().getUri().getBaseUri()).path("/").build(realm.getName()).toString(); + } + @GET @Produces(MediaType.APPLICATION_JSON) @Operation(summary = "Get invitations for the organization") diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/ClientAttributeUpdater.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/ClientAttributeUpdater.java index 79ba0777083..ad258fef93f 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/ClientAttributeUpdater.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/ClientAttributeUpdater.java @@ -155,6 +155,11 @@ public class ClientAttributeUpdater extends ServerResourceUpdater users = testRealm().users().searchByEmail(email, true); + assertThat(users, not(empty())); + MemberRepresentation member = organization.members().member(users.get(0).getId()).toRepresentation(); + Assert.assertNotNull(member); + assertThat(member.getMembershipType(), equalTo(MembershipType.MANAGED)); + getCleanup().addCleanup(() -> testRealm().users().get(users.get(0).getId()).remove()); + + assertThat(driver.getTitle(), containsString("AUTH_RESPONSE")); + } + } + @Test public void testInviteNewUserRegistration() throws IOException, MessagingException { String email = "inviteduser@email";