Skip to content

Commit 6742c4b

Browse files
stianstgraziang
andauthored
User validation in JWT Authorization Grant (#346)
Fixes CVE-2026-1609 Signed-off-by: Giuseppe Graziano <g.graziano94@gmail.com> Co-authored-by: Giuseppe Graziano <g.graziano94@gmail.com>
1 parent b5fb9f9 commit 6742c4b

3 files changed

Lines changed: 39 additions & 0 deletions

File tree

docs/guides/securing-apps/jwt-authorization-grant.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ The exact processing that {project_name} performs over the assertion is the foll
4040
. Other claims like `nbf` (not before), `iat` (issued at) and `jti` (JWT ID) can be present and should be validated in that case.
4141
. The JWT should be signed and its signature should be verified with the keys associated to the identity provider in {project_name}.
4242
43+
NOTE: Brute force protection is not applied to the JWT Authorization Grant for temporarily locked users, since this grant type does not perform user credential-based authentication but relies on an assertion issued by an external identity provider, and therefore cannot be compromised by brute force attacks on credentials of {project_name} user.
44+
4345
== Configuration
4446
4547
Only confidential clients can request a JWT authorization grant. In order to allow a client to send such a grant, the client should be configured accordingly. Using the admin console, **clients** -> select your client -> **Settings** tab -> **Capability config** section.

services/src/main/java/org/keycloak/protocol/oidc/grants/JWTAuthorizationGrantType.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ public Response process(Context context) {
111111
if (user == null) {
112112
throw new RuntimeException("User not found");
113113
}
114+
if (!user.isEnabled()) {
115+
throw new RuntimeException("User is not enabled");
116+
}
117+
if (user.getRequiredActionsStream().findAny().isPresent()) {
118+
throw new RuntimeException("Account is not fully set up");
119+
}
114120
event.user(user);
115121
event.detail(Details.USERNAME, user.getUsername());
116122

tests/base/src/test/java/org/keycloak/tests/oauth/AbstractJWTAuthorizationGrantTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.keycloak.tests.oauth;
22

3+
import java.util.Collections;
34
import java.util.List;
45

56
import org.keycloak.OAuth2Constants;
@@ -11,6 +12,7 @@
1112
import org.keycloak.representations.AccessToken;
1213
import org.keycloak.representations.IDToken;
1314
import org.keycloak.representations.JsonWebToken;
15+
import org.keycloak.representations.idm.UserRepresentation;
1416
import org.keycloak.testframework.events.EventAssertion;
1517
import org.keycloak.testframework.oauth.OAuthIdentityProvider;
1618
import org.keycloak.testsuite.util.oauth.AccessTokenResponse;
@@ -309,4 +311,33 @@ public void testDisabledIdentityProvider() {
309311
AccessTokenResponse response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
310312
assertFailure("Identity Provider is not enabled", response, events.poll());
311313
}
314+
315+
@Test
316+
public void testUserDisabled() {
317+
UserRepresentation userRep = user.admin().toRepresentation();
318+
userRep.setEnabled(false);
319+
user.admin().update(userRep);
320+
321+
String jwt = getIdentityProvider().encodeToken(createDefaultAuthorizationGrantToken());
322+
AccessTokenResponse response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
323+
assertFailure("User is not enabled", response, events.poll());
324+
325+
userRep.setEnabled(true);
326+
user.admin().update(userRep);
327+
}
328+
329+
@Test
330+
public void testUserWithRequiredAction() {
331+
UserRepresentation userRep = user.admin().toRepresentation();
332+
userRep.setRequiredActions(Collections.singletonList("UPDATE_PASSWORD"));
333+
user.admin().update(userRep);
334+
335+
String jwt = getIdentityProvider().encodeToken(createDefaultAuthorizationGrantToken());
336+
AccessTokenResponse response = oAuthClient.jwtAuthorizationGrantRequest(jwt).send();
337+
assertFailure("Account is not fully set up", response, events.poll());
338+
339+
userRep = user.admin().toRepresentation();
340+
userRep.setRequiredActions(Collections.emptyList());
341+
user.admin().update(userRep);
342+
}
312343
}

0 commit comments

Comments
 (0)