From a2c6752715edcf08d97f2ae4dd1cd34a1c4751e9 Mon Sep 17 00:00:00 2001 From: andrew Date: Sat, 25 Jul 2026 19:24:38 +0200 Subject: [PATCH] Gracefully handle disabled or removed IdP during logout When a user logs in through an identity provider and that provider is later disabled or deleted, browserLogout throws an IdentityBrokerException that breaks the entire logout flow. This wraps the identity provider lookup in a try-catch so that the logout completes locally even when the broker is no longer available, logging a warning instead of failing. Closes #51123 Signed-off-by: andrew --- .../managers/AuthenticationManager.java | 16 +++-- .../broker/KcOidcBrokerLogoutTest.java | 66 +++++++++++++++++++ 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index 94aa83df73b2..5de0f2752d8f 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -733,10 +733,18 @@ public static Response browserLogout(KeycloakSession session, String brokerId = userSession.getNote(Details.IDENTITY_PROVIDER); String initiatingIdp = logoutAuthSession.getAuthNote(AuthenticationManager.LOGOUT_INITIATING_IDP); if (brokerId != null && !brokerId.equals(initiatingIdp)) { - UserAuthenticationIdentityProvider identityProvider = IdentityBrokerService.getIdentityProvider(session, brokerId); - Response response = identityProvider.keycloakInitiatedBrowserLogout(session, userSession, uriInfo, realm); - if (response != null) { - return response; + UserAuthenticationIdentityProvider identityProvider = null; + try { + identityProvider = IdentityBrokerService.getIdentityProvider(session, brokerId); + } catch (IdentityBrokerException e) { + logger.warnf("Identity provider [%s] is no longer available, skipping Keycloak-initiated broker logout for user session [%s]", brokerId, userSession.getId()); + } + + if (identityProvider != null) { + Response response = identityProvider.keycloakInitiatedBrowserLogout(session, userSession, uriInfo, realm); + if (response != null) { + return response; + } } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLogoutTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLogoutTest.java index 7e9236bccf67..ce446da71f6f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLogoutTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcOidcBrokerLogoutTest.java @@ -289,4 +289,70 @@ public void testFrontChannelLogoutRequestsSendingOnlyClientIdWithFrontChannelLog identityProviderResource.update(representation); } } + + @Test + public void logoutSucceedsWhenIdpIsDisabled() { + logInAsUserInIDPForFirstTime(); + Assertions.assertTrue(oauth.parseLoginResponse().isSuccess()); + + // Disable the identity provider while the user session is still active + RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName()); + IdentityProviderResource idpResource = consumerRealm.identityProviders().get(bc.getIDPAlias()); + IdentityProviderRepresentation idpRep = idpResource.toRepresentation(); + idpRep.setEnabled(false); + idpResource.update(idpRep); + + try { + // Browser logout should complete gracefully even though the IdP is disabled + logoutFromRealm( + getConsumerRoot(), + bc.consumerRealmName(), + null, + null, + null, + null + ); + + // Verify user is actually logged out + oauth.client("broker-app"); + oauth.realm(bc.consumerRealmName()); + oauth.openLoginForm(); + waitForPage(driver, "sign in to", true); + } finally { + idpRep.setEnabled(true); + idpResource.update(idpRep); + } + } + + @Test + public void logoutSucceedsWhenIdpIsRemoved() { + logInAsUserInIDPForFirstTime(); + Assertions.assertTrue(oauth.parseLoginResponse().isSuccess()); + + // Remove the identity provider entirely while the user session is still active + RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName()); + IdentityProviderResource idpResource = consumerRealm.identityProviders().get(bc.getIDPAlias()); + IdentityProviderRepresentation idpRep = idpResource.toRepresentation(); + idpResource.remove(); + + try { + // Browser logout should complete gracefully even though the IdP no longer exists + logoutFromRealm( + getConsumerRoot(), + bc.consumerRealmName(), + null, + null, + null, + null + ); + + // Verify user is actually logged out + oauth.client("broker-app"); + oauth.realm(bc.consumerRealmName()); + oauth.openLoginForm(); + waitForPage(driver, "sign in to", true); + } finally { + consumerRealm.identityProviders().create(idpRep).close(); + } + } }