VYPR
High severityNVD Advisory· Published Mar 5, 2026· Updated Mar 24, 2026

Org.keycloak/keycloak-services: improper enforcement of disabled identity provider in identitybrokerservice (authentication bypass)

CVE-2026-3009

Description

A security flaw in the IdentityBrokerService.performLogin endpoint of Keycloak allows authentication to proceed using an Identity Provider (IdP) even after it has been disabled by an administrator. An attacker who knows the IdP alias can reuse a previously generated login request to bypass the administrative restriction. This undermines access control enforcement and may allow unauthorized authentication through a disabled external provider.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Keycloak's IdentityBrokerService.performLogin endpoint permits authentication through a disabled IdP, allowing attackers to bypass administrative access controls.

Vulnerability

Overview

The IdentityBrokerService.performLogin endpoint in Keycloak fails to properly enforce the enabled/disabled state of an Identity Provider (IdP) during the authentication flow. An attacker who knows the IdP alias can reuse a previously generated login request to authenticate through a provider that an administrator has disabled. This flaw is rooted in a missing enabled-state check in the brokering flow logic [1][2].

Exploitation

Details

An attacker must know the alias of a disabled IdP and have access to a previously valid login request URL or token. No authentication is required to initiate the request to the performLogin endpoint. The vulnerability can be triggered remotely over the network without special privileges, though some user interaction may be required to complete the authentication flow [2][3]. The fix in commit 4fd5367 introduces a getIdentityProviderModel method that verifies the IdP exists and is enabled before proceeding [3].

Impact

Successful exploitation allows an attacker to bypass administrative restrictions on disabled identity providers, potentially gaining unauthorized access to applications relying on Keycloak for authentication. This undermines access control enforcement and may lead to privilege escalation or lateral movement within the federated environment [1][2].

Mitigation

Red Hat has released updated images in Red Hat build of Keycloak 26.4.10 as part of RHSA-2026:3948 to address this issue [4]. Users should apply the update immediately. No workaround is documented. CVE-2026-3009 is not currently listed in the CISA Known Exploited Vulnerabilities catalog.

References

[1] Red Hat CVE page [2] NVD entry [3] GitHub commit fixing the missing check [4] Red Hat Security Advisory RHSA-2026:3948

AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.keycloak:keycloak-servicesMaven
< 26.5.526.5.5

Affected products

5
  • Red Hat/Red Hat build of Keycloak 26.4.10v5
    cpe:/a:redhat:build_keycloak:26.4::el9
  • Red Hat/Red Hat JBoss Enterprise Application Platform Expansion Packv5
    cpe:/a:redhat:jbosseapxp
  • Red Hat/Red Hat JBoss Enterprise Application Platform 8v5
    cpe:/a:redhat:jboss_enterprise_application_platform:8
  • Red Hat/Red Hat Single Sign-On 7v5
    cpe:/a:redhat:red_hat_single_sign_on:7

Patches

1
4fd5367e6cc2

Enforce disabled checks when processing brokering flows (#367)

https://github.com/keycloak/keycloakPedro IgorFeb 25, 2026via ghsa
7 files changed · +101 13
  • server-spi-private/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java+12 2 modified
    @@ -22,6 +22,7 @@
     import java.util.HashSet;
     import java.util.List;
     import java.util.Map;
    +import java.util.Objects;
     import java.util.Set;
     
     import org.keycloak.models.Constants;
    @@ -53,15 +54,24 @@ public class BrokeredIdentityContext {
         private AuthenticationSessionModel authenticationSession;
     
         public BrokeredIdentityContext(String id, IdentityProviderModel idpConfig) {
    -        if (id == null) {
    -            throw new RuntimeException("No identifier provider for identity.");
    +        Objects.requireNonNull(id, "id must not be null");
    +        Objects.requireNonNull(idpConfig, "Identity provider config must not be null");
    +
    +        if (!idpConfig.isEnabled()) {
    +            throw new IdentityBrokerException("Identity provider is disabled");
             }
     
             this.id = id;
             this.idpConfig = idpConfig;
         }
     
         public BrokeredIdentityContext(IdentityProviderModel idpConfig) {
    +        Objects.requireNonNull(idpConfig, "Identity provider config must not be null");
    +
    +        if (!idpConfig.isEnabled()) {
    +            throw new IdentityBrokerException("Identity provider is disabled");
    +        }
    +
             this.idpConfig = idpConfig;
         }
     
    
  • services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java+1 1 modified
    @@ -268,7 +268,7 @@ public BrokeredIdentityContext deserialize(KeycloakSession session, Authenticati
             RealmModel realm = authSession.getRealm();
             IdentityProviderModel idpConfig = session.identityProviders().getByAlias(getIdentityProviderId());
     
    -        if (idpConfig == null) {
    +        if (idpConfig == null || !idpConfig.isEnabled()) {
                 throw new ModelException("Can't find identity provider with ID " + getIdentityProviderId() + " in realm " + realm.getName());
             }
     
    
  • services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java+14 9 modified
    @@ -130,11 +130,6 @@
     
     import static org.keycloak.broker.provider.AbstractIdentityProvider.BROKER_REGISTERED_NEW_USER;
     
    -/**
    - * <p></p>
    - *
    - * @author Pedro Igor
    - */
     public class IdentityBrokerService implements UserAuthenticationIdentityProvider.AuthenticationCallback {
     
         // Authentication session note, which references identity provider that is currently linked
    @@ -301,7 +296,7 @@ public Response clientInitiatedAccountLinking(@PathParam("provider_alias") Strin
             }
     
     
    -        IdentityProviderModel identityProviderModel = session.identityProviders().getByAlias(providerAlias);
    +        IdentityProviderModel identityProviderModel = getIdentityProviderModel(session, providerAlias);
             if (identityProviderModel == null) {
                 event.error(Errors.UNKNOWN_IDENTITY_PROVIDER);
                 UriBuilder builder = UriBuilder.fromUri(redirectUri)
    @@ -341,6 +336,16 @@ public Response clientInitiatedAccountLinking(@PathParam("provider_alias") Strin
             return performClientInitiatedAccountLogin(providerAlias, clientSessionCode);
         }
     
    +    private static IdentityProviderModel getIdentityProviderModel(KeycloakSession session, String providerAlias) {
    +        IdentityProviderModel model = session.identityProviders().getByAlias(providerAlias);
    +
    +        if (model == null || !model.isEnabled()) {
    +            throw new IdentityBrokerException("Identity Provider [" + providerAlias + "] not found.");
    +        }
    +
    +        return model;
    +    }
    +
         public Response performClientInitiatedAccountLogin(String providerAlias, ClientSessionCode<AuthenticationSessionModel> clientSessionCode) {
             try {
                 UserAuthenticationIdentityProvider<?> identityProvider = getIdentityProvider(session, providerAlias);
    @@ -394,7 +399,7 @@ public Response performLogin(@PathParam("provider_alias") String providerAlias,
     
                 ClientSessionCode<AuthenticationSessionModel> clientSessionCode = new ClientSessionCode<>(session, realmModel, authSession);
                 clientSessionCode.setAction(AuthenticationSessionModel.Action.AUTHENTICATE.name());
    -            IdentityProviderModel identityProviderModel = session.identityProviders().getByAlias(providerAlias);
    +            IdentityProviderModel identityProviderModel = getIdentityProviderModel(session, providerAlias);
                 if (identityProviderModel == null) {
                     throw new IdentityBrokerException("Identity Provider [" + providerAlias + "] not found.");
                 }
    @@ -1352,7 +1357,7 @@ private Response notFound(String message) {
         }
     
         public static UserAuthenticationIdentityProvider<?> getIdentityProvider(KeycloakSession session, String alias) {
    -        IdentityProviderModel identityProviderModel = session.identityProviders().getByAlias(alias);
    +        IdentityProviderModel identityProviderModel = getIdentityProviderModel(session, alias);
             UserAuthenticationIdentityProvider<?> identityProvider = getIdentityProvider(session, identityProviderModel, UserAuthenticationIdentityProvider.class);
             if (identityProvider == null) {
                 throw new IdentityBrokerException("Identity Provider [" + alias + "] not found.");
    @@ -1383,7 +1388,7 @@ private static IdentityProviderFactory<?> getIdentityProviderFactory(KeycloakSes
         }
     
         private IdentityProviderModel getIdentityProviderConfig(String providerAlias) {
    -        IdentityProviderModel model = session.identityProviders().getByAlias(providerAlias);
    +        IdentityProviderModel model = getIdentityProviderModel(session, providerAlias);
             if (model == null) {
                 throw new IdentityBrokerException("Configuration for identity provider [" + providerAlias + "] not found.");
             }
    
  • services/src/test/java/org/keycloak/test/broker/oidc/AbstractOAuth2IdentityProviderTest.java+1 0 modified
    @@ -175,6 +175,7 @@ private static class TestProvider extends AbstractOAuth2IdentityProvider<OAuth2I
     
     		public TestProvider(OAuth2IdentityProviderConfig config) {
     			super(null, config);
    +            config.setEnabled(true);
     		}
     
     		@Override
    
  • services/src/test/java/org/keycloak/test/broker/saml/XPathAttributeMapperTest.java+9 1 modified
    @@ -115,7 +115,7 @@ private String testMapping(String attributeValue, String xpath, String attribute
             config.put(XPathAttributeMapper.ATTRIBUTE_NAME, attributeNameToSearch);
             config.put(XPathAttributeMapper.USER_ATTRIBUTE, attribute);
             config.put(XPathAttributeMapper.ATTRIBUTE_XPATH, xpath);
    -        BrokeredIdentityContext context = new BrokeredIdentityContext("brokeredIdentityContext", new IdentityProviderModel());
    +        BrokeredIdentityContext context = new BrokeredIdentityContext("brokeredIdentityContext", createIdentityProviderModel());
             AssertionType assertion = AssertionUtil.createAssertion("assertionId", NameIDType.deserializeFromString("nameIDType"));
             AttributeStatementType statement = new AttributeStatementType();
             assertion.addStatement(statement);
    @@ -137,4 +137,12 @@ private String testMapping(String attributeValue, String xpath, String attribute
             Object userAttributes = context.getContextData().get("user.attributes." + attribute);
             return userAttributes == null ? null : ((List<?>) userAttributes).get(0).toString();
         }
    +
    +    private IdentityProviderModel createIdentityProviderModel() {
    +        IdentityProviderModel model = new IdentityProviderModel();
    +
    +        model.setEnabled(true);
    +
    +        return model;
    +    }
     }
    
  • testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/AbstractAdvancedBrokerTest.java+33 0 modified
    @@ -56,6 +56,7 @@
     
     import static org.hamcrest.MatcherAssert.assertThat;
     import static org.hamcrest.Matchers.hasItems;
    +import static org.hamcrest.Matchers.is;
     import static org.hamcrest.Matchers.not;
     import static org.hamcrest.Matchers.notNullValue;
     import static org.junit.Assert.assertEquals;
    @@ -614,4 +615,36 @@ public void testWithLinkedFederationProvider() {
                 removeUserByUsername(adminClient.realm(bc.consumerRealmName()), "test-user-noemail");
             }
         }
    +
    +    @Test
    +    public void testDisabledBroker() {
    +        loginUser();
    +        logoutFromConsumerRealm();
    +        AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
    +
    +        oauth.clientId("broker-app");
    +        loginPage.open(bc.consumerRealmName());
    +
    +        RealmResource realm = adminClient.realm(bc.consumerRealmName());
    +        identityProviderResource = realm.identityProviders().get(bc.getIDPAlias());
    +        IdentityProviderRepresentation idpRep = identityProviderResource.toRepresentation();
    +        idpRep.setEnabled(false);
    +        identityProviderResource.update(idpRep);
    +        waitForPage(driver, "sign in to", true);
    +        loginPage.clickSocial(bc.getIDPAlias());
    +        errorPage.assertCurrent();
    +        assertThat(errorPage.getError(), is("Could not send authentication request to identity provider."));
    +
    +        idpRep.setEnabled(true);
    +        identityProviderResource.update(idpRep);
    +        oauth.openLoginForm();
    +        loginPage.clickSocial(bc.getIDPAlias());
    +        waitForPage(driver, "sign in to", true);
    +        Assert.assertTrue("Driver should be on the provider realm page right now", driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
    +        idpRep.setEnabled(false);
    +        identityProviderResource.update(idpRep);
    +        loginPage.login(bc.getUserLogin(), bc.getUserPassword());
    +        errorPage.assertCurrent();
    +        assertThat(errorPage.getError(), is("Page not found"));
    +    }
     }
    
  • testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/broker/KcSamlIdPInitiatedSsoTest.java+31 0 modified
    @@ -136,6 +136,7 @@ public void resetPrincipalType() {
             IdentityProviderRepresentation rep = idp.toRepresentation();
             rep.getConfig().put(SAMLIdentityProviderConfig.NAME_ID_POLICY_FORMAT, JBossSAMLURIConstants.NAMEID_FORMAT_PERSISTENT.get());
             rep.getConfig().put(SAMLIdentityProviderConfig.PRINCIPAL_TYPE, SamlPrincipalType.SUBJECT.name());
    +        rep.setEnabled(true);
             idp.update(rep);
         }
     
    @@ -188,6 +189,36 @@ public void testProviderIdpInitiatedLogin() throws Exception {
             assertThat(driver.findElement(By.tagName("a")).getAttribute("id"), containsString("account"));
         }
     
    +    @Test
    +    public void testDisabledBroker() throws Exception {
    +        driver.navigate().to(getSamlIdpInitiatedUrl(REALM_PROV_NAME, "samlbroker"));
    +
    +        waitForPage("sign in to", true);
    +
    +        assertThat("Driver should be on the provider realm page right now",
    +                driver.getCurrentUrl(), containsString("/auth/realms/" + REALM_PROV_NAME + "/"));
    +
    +        log.debug("Logging in");
    +        accountLoginPage.login(PROVIDER_REALM_USER_NAME, PROVIDER_REALM_USER_PASSWORD);
    +
    +        waitForPage("update account information", false);
    +
    +        Assert.assertTrue(updateAccountInformationPage.isCurrent());
    +        assertThat("We must be on consumer realm right now",
    +                driver.getCurrentUrl(), containsString("/auth/realms/" + REALM_CONS_NAME + "/"));
    +
    +        log.debug("Updating info on updateAccount page");
    +        updateAccountInformationPage.updateAccountInformation(CONSUMER_CHOSEN_USERNAME, "test@localhost", "Firstname", "Lastname");
    +
    +        IdentityProviderResource idp = adminClient.realm(REALM_CONS_NAME).identityProviders().get("saml-leaf");
    +        IdentityProviderRepresentation rep = idp.toRepresentation();
    +        rep.setEnabled(false);
    +        idp.update(rep);
    +        driver.navigate().to(getSamlIdpInitiatedUrl(REALM_PROV_NAME, "samlbroker"));
    +        errorPage.assertCurrent();
    +        assertThat(errorPage.getError(), is("Page not found"));
    +    }
    +
         private String getSamlIdpInitiatedUrl(String realmName, String samlIdpInitiatedSsoUrlName) {
             return getAuthRoot() + "/auth/realms/" + realmName + "/protocol/saml/clients/" + samlIdpInitiatedSsoUrlName;
         }
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.