High severity7.5NVD Advisory· Published Apr 2, 2026· Updated Apr 16, 2026
CVE-2026-4634
CVE-2026-4634
Description
A flaw was found in Keycloak. An unauthenticated attacker can exploit this vulnerability by sending a specially crafted POST request with an excessively long scope parameter to the OpenID Connect (OIDC) token endpoint. This leads to high resource consumption and prolonged processing times, ultimately resulting in a Denial of Service (DoS) for the Keycloak server.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.keycloak:keycloak-servicesMaven | < 26.5.7 | 26.5.7 |
Affected products
5cpe:2.3:a:redhat:build_of_keycloak:-:*:*:*:text-only:*:*:*+ 4 more
- cpe:2.3:a:redhat:build_of_keycloak:-:*:*:*:text-only:*:*:*
- cpe:2.3:a:redhat:build_of_keycloak:26.2:*:*:*:text-only:*:*:*
- cpe:2.3:a:redhat:build_of_keycloak:26.2.15:*:*:*:text-only:*:*:*
- cpe:2.3:a:redhat:build_of_keycloak:26.4:*:*:*:text-only:*:*:*
- cpe:2.3:a:redhat:build_of_keycloak:26.4.11:*:*:*:text-only:*:*:*
Patches
1b455ee4f28abImprove performance of scope processing in TokenManager. Limit for maximum length of OIDC parameters in Token endpoint (#478)
6 files changed · +89 −9
docs/documentation/upgrading/topics/changes/changes-26_5_7.adoc+12 −0 added@@ -0,0 +1,12 @@ +== Notable changes + +Notable changes may include internal behavior changes that prevent common misconfigurations, bugs that are fixed, or changes to simplify running {project_name}. +It also lists significant changes to internal APIs. + +=== Maximum length of the parameters in the OIDC token endpoint + +When the OIDC token endpoint request (or OAuth2 token endpoint request) is sent, a new limit exists for the maximum length of every OIDC/OAuth2 parameter. The maximum length of each parameter is 4,000 characters, +which is aligned with the same limit, which already exists for the parameters sent to OIDC/OAuth authentication request. + +If you want to increase or lower those numbers, start the server with the option `req-params-default-max-size` for the default maximum length of the +OIDC/OAuth2 parameters or you can use something such as `req-params-max-size` for one specific parameter. For more details, see the `login-protocol` provider configuration in the link:{allproviderconfigguide_link}[{allproviderconfigguide_name}].
docs/documentation/upgrading/topics/changes/changes.adoc+4 −0 modified@@ -1,6 +1,10 @@ [[migration-changes]] == Migration Changes +=== Migrating to 26.5.7 + +include::changes-26_5_7.adoc[leveloffset=2] + === Migrating to 26.5.6 include::changes-26_5_6.adoc[leveloffset=2]
services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java+29 −0 modified@@ -18,6 +18,8 @@ package org.keycloak.protocol.oidc.endpoints; import java.io.IOException; +import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.xml.namespace.QName; @@ -43,8 +45,10 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.protocol.LoginProtocol; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCLoginProtocol; +import org.keycloak.protocol.oidc.OIDCProviderConfig; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.grants.OAuth2GrantType; import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory; @@ -137,6 +141,8 @@ public Response processGrantRequest() { checkParameterDuplicated(); } + checkParameters(); + /* * To request an access token that is bound to a public key using DPoP, the client MUST provide a valid DPoP * proof JWT in a DPoP header when making an access token request to the authorization server's token endpoint. @@ -219,6 +225,29 @@ private void checkParameterDuplicated() { } } + protected void checkParameters() { + OIDCLoginProtocol loginProtocol = (OIDCLoginProtocol) session.getProvider(LoginProtocol.class, OIDCLoginProtocol.LOGIN_PROTOCOL); + OIDCProviderConfig config = loginProtocol.getConfig(); + + Map<String, List<String>> paramsCopy = new HashMap<>(formParams); + for (Map.Entry<String, List<String>> param : paramsCopy.entrySet()) { + String paramName = param.getKey(); + int totalLengthOfParamValues = param.getValue().stream() + .map(String::length) + .reduce(0, Integer::sum); + int maxLength = config.getMaxLengthForTheParameter(paramName); + if (totalLengthOfParamValues > maxLength) { + logger.warnf("The size of OIDC parameter '%s' is longer (%d) than allowed (%d). %s", paramName, totalLengthOfParamValues, maxLength, config.isAdditionalReqParamsFailFast() ? "Request not allowed." : "Ignoring the parameter."); + if (config.isAdditionalReqParamsFailFast()) { + throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, "The size of OIDC parameter '" + paramName + "' is longer than allowed.", + Response.Status.BAD_REQUEST); + } else { + formParams.remove(paramName); + } + } + } + } + public static class TokenExchangeSamlProtocol extends SamlProtocol { final SamlClient samlClient;
services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolFactory.java+5 −5 modified@@ -572,20 +572,20 @@ public List<ProviderConfigProperty> getConfigMetadata() { .property() .name(CONFIG_OIDC_REQ_PARAMS_DEFAULT_MAX_SIZE) .type("int") - .helpText("Maximum default length of the standard OIDC parameter sent to the OIDC authentication request. This applies to most of the standard parameters like for example 'state', 'nonce' etc." + + .helpText("Maximum default length of the standard OIDC parameter sent to the OIDC authentication or token endpoints. This applies to most of the standard parameters like for example 'state', 'nonce' etc." + " The exception is 'login_hint' parameter, which has maximum length of 255 characters.") .defaultValue(DEFAULT_REQ_PARAMS_DEFAULT_MAX_SIZE) .add() .property() .name(CONFIG_OIDC_REQ_PARAMS_MAX_SIZE_PREFIX + "--" + OIDCLoginProtocol.LOGIN_HINT_PARAM) .type("int") - .helpText("Maximum length of the standard OIDC authentication request parameter overriden for the specified parameter. Useful if some standard OIDC parameter should have different limit than '" + CONFIG_OIDC_REQ_PARAMS_DEFAULT_MAX_SIZE + + .helpText("Maximum length of the standard parameter sent to OIDC authentication or token endpoints overriden for the specified parameter. Useful if some standard OIDC parameter should have different limit than '" + CONFIG_OIDC_REQ_PARAMS_DEFAULT_MAX_SIZE + "'. It is needed to add the name of the parameter after this prefix into the configuration. In this example, the '" + OIDCLoginProtocol.LOGIN_HINT_PARAM + "' parameter is used, but this format is supported for any known standard OIDC/OAuth2 parameter.") .add() .property() .name(CONFIG_OIDC_ADD_REQ_PARAMS_MAX_NUMBER) .type("int") - .helpText("Maximum number of additional request parameters sent to the OIDC authentication request. As 'additional request parameter' is meant some custom parameter not directly treated as standard OIDC/OAuth2 protocol parameter. Additional parameters might be useful for example to add custom claims to the OIDC token (in case that also particular protocol mappers are configured).") + .helpText("Maximum number of additional request parameters sent to the OIDC authentication or token endpoints. As 'additional request parameter' is meant some custom parameter not directly treated as standard OIDC/OAuth2 protocol parameter. Additional parameters might be useful for example to add custom claims to the OIDC token (in case that also particular protocol mappers are configured).") .defaultValue(DEFAULT_ADDITIONAL_REQ_PARAMS_MAX_NUMBER) .add() .property() @@ -603,8 +603,8 @@ public List<ProviderConfigProperty> getConfigMetadata() { .property() .name(CONFIG_OIDC_ADD_REQ_PARAMS_FAIL_FAST) .type("boolean") - .helpText("Whether the fail-fast strategy should be enforced in case if the limit for some standard OIDC parameter or additional OIDC parameter is not met for the parameters sent to the OIDC authentication request." + - " If false, then all additional request parameters to not meet the configuration are silently ignored. If true, an exception will be raised and OIDC authentication request will not be allowed.") + .helpText("Whether the fail-fast strategy should be enforced in case if the limit for some standard OIDC parameter or additional OIDC parameter is not met for the parameters sent to the OIDC authentication or token endpoints." + + " If false, then all additional request parameters to not meet the configuration are silently ignored. If true, an exception will be raised and request to the OIDC authentication or token endpoints will not be allowed.") .defaultValue(DEFAULT_ADDITIONAL_REQ_PARAMS_FAIL_FAST) .add() .build();
services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java+9 −4 modified@@ -698,6 +698,7 @@ public static Stream<ClientScopeModel> getRequestedClientScopes(KeycloakSession Map<String, ClientScopeModel> allOptionalScopes = client.getClientScopes(false); + OrganizationScope orgScope = tryResolveOrganizationScope(session, scopeParam, user); // Add optional client scopes requested by scope parameter return Stream.concat(parseScopeParameter(scopeParam) .map(name -> { @@ -707,13 +708,13 @@ public static Stream<ClientScopeModel> getRequestedClientScopes(KeycloakSession return scope; } - return tryResolveDynamicClientScope(session, scopeParam, user, name); + return tryResolveOrganizationClientScope(session, user, orgScope, name); }) .filter(Objects::nonNull), clientScopes).distinct(); } - private static ClientScopeModel tryResolveDynamicClientScope(KeycloakSession session, String scopeParam, UserModel user, String name) { + private static OrganizationScope tryResolveOrganizationScope(KeycloakSession session, String scopeParam, UserModel user) { if (Profile.isFeatureEnabled(Feature.ORGANIZATION)) { OrganizationScope orgScope = OrganizationScope.valueOfScope(session, scopeParam); @@ -725,10 +726,14 @@ private static ClientScopeModel tryResolveDynamicClientScope(KeycloakSession ses return null; } - return orgScope.toClientScope(name, user, session); + return orgScope; + } else { + return null; } + } - return null; + private static ClientScopeModel tryResolveOrganizationClientScope(KeycloakSession session, UserModel user, OrganizationScope orgScope, String name) { + return orgScope == null ? null : orgScope.toClientScope(name, user, session); } /**
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oidc/OIDCScopeTest.java+30 −0 modified@@ -53,11 +53,13 @@ import org.keycloak.testsuite.util.RoleBuilder; import org.keycloak.testsuite.util.UserBuilder; import org.keycloak.testsuite.util.oauth.AccessTokenResponse; +import org.keycloak.util.TokenUtil; import org.junit.After; import org.junit.Before; import org.junit.Test; +import static org.keycloak.OAuthErrorException.INVALID_SCOPE; import static org.keycloak.testsuite.auth.page.AuthRealm.TEST; import static org.junit.Assert.assertEquals; @@ -654,6 +656,34 @@ public void testClientScopesPermissions() { testApp.removeOptionalClientScope(scopeParentId); } + @Test + public void testLengthyScopeParameter() { + // Scope parameter too long (longer than 4000 characters). Will be ignored + String scope = getLongScopeParameter(1000); + oauth.scope(scope); + AccessTokenResponse response = oauth.doPasswordGrantRequest("john", "password"); + assertEquals(200, response.getStatusCode()); + AccessToken token = oauth.verifyToken(response.getAccessToken()); + Assert.assertFalse(TokenUtil.isOIDCRequest(token.getScope())); + + // Scope parameter relatively long. Should not be ignored + scope = getLongScopeParameter(800); + oauth.scope(scope); + response = oauth.doPasswordGrantRequest("john", "password"); + assertEquals(400, response.getStatusCode()); + assertEquals(INVALID_SCOPE, response.getError()); + } + + // Get very long "scope" parameter created from big list of some unknown scopes + private String getLongScopeParameter(int scopesCount) { + StringBuilder scopeParam = new StringBuilder("openid"); + for (int i = 0 ; i < scopesCount ; i++) { + scopeParam.append(" s").append(i); + } + String scope = scopeParam.toString(); + getLogger().infof("Scopes count: %d, Scope param length: %d", scopesCount, scope.length()); + return scope; + } private void testLoginAndClientScopesPermissions(String username, String expectedRoleScopes, String... expectedRoles) { String userId = ApiUtil.findUserByUsername(testRealm(), username).getId();
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
10- access.redhat.com/errata/RHSA-2026:6475nvdVendor AdvisoryWEB
- access.redhat.com/errata/RHSA-2026:6476nvdVendor AdvisoryWEB
- access.redhat.com/errata/RHSA-2026:6477nvdVendor AdvisoryWEB
- access.redhat.com/errata/RHSA-2026:6478nvdVendor AdvisoryWEB
- access.redhat.com/security/cve/CVE-2026-4634nvdVendor AdvisoryWEB
- bugzilla.redhat.com/show_bug.cginvdIssue TrackingVendor AdvisoryWEB
- github.com/advisories/GHSA-h4wv-g838-66g3ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2026-4634ghsaADVISORY
- github.com/keycloak/keycloak/commit/b455ee4f28abb6f2120aff72fd179589cc5267a0ghsaWEB
- github.com/keycloak/keycloak/issues/47716ghsaWEB
News mentions
0No linked articles in our index yet.