This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch 3.6.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit 1dbce6967a13c0b7acf8185c1b2f191e81839cee
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu Jul 9 10:04:22 2026 +0100

    Enforce that Access tokens only are accepted in JwtAccessTokenValidator 
(#3294)
    
    (cherry picked from commit 6af78b897f71724574629cfec6cb556cc896686a)
---
 .../oauth2/filters/JwtAccessTokenValidator.java    | 17 ++++++++++
 .../filters/JwtAccessTokenValidatorTest.java       | 37 ++++++++++++++++++++--
 2 files changed, 52 insertions(+), 2 deletions(-)

diff --git 
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidator.java
 
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidator.java
index b1127334032..8b2bedca82b 100644
--- 
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidator.java
+++ 
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidator.java
@@ -46,6 +46,9 @@ import org.apache.cxf.rs.security.oauth2.utils.OAuthUtils;
 public class JwtAccessTokenValidator extends JoseJwtConsumer implements 
AccessTokenValidator {
 
     private static final String USERNAME_PROP = "username";
+    private static final String TOKEN_USE_CLAIM = "token_use";
+    private static final String ACCESS_TOKEN_USE = "access";
+    private static final String INVALID_TOKEN_TYPE = "Invalid token type";
 
     private Map<String, String> jwtAccessTokenClaimMap;
     private boolean validateAudience = true;
@@ -69,6 +72,8 @@ public class JwtAccessTokenValidator extends JoseJwtConsumer 
implements AccessTo
 
     @Override
     protected void validateToken(JwtToken jwt) {
+        validateTokenType(jwt);
+
         // We must have an issuer
         if (jwt.getClaim(JwtConstants.CLAIM_ISSUER) == null) {
             throw new OAuthServiceException(OAuthConstants.INVALID_GRANT);
@@ -77,6 +82,18 @@ public class JwtAccessTokenValidator extends JoseJwtConsumer 
implements AccessTo
         JwtUtils.validateTokenClaims(jwt.getClaims(), getTtl(), 
getClockOffset(), isValidateAudience());
     }
 
+    private void validateTokenType(JwtToken jwt) {
+        Object tokenType = jwt.getJwsHeader(JoseConstants.HEADER_TYPE);
+        if (tokenType != null && 
!JoseConstants.TYPE_AT_JWT.equals(tokenType.toString())) {
+            throw new OAuthServiceException(INVALID_TOKEN_TYPE);
+        }
+
+        String tokenUse = jwt.getClaims().getStringProperty(TOKEN_USE_CLAIM);
+        if (tokenUse != null && !ACCESS_TOKEN_USE.equals(tokenUse)) {
+            throw new OAuthServiceException(INVALID_TOKEN_TYPE);
+        }
+    }
+
     private AccessTokenValidation convertClaimsToValidation(JwtClaims claims) {
         AccessTokenValidation atv = new AccessTokenValidation();
         atv.setInitialValidationSuccessful(true);
diff --git 
a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidatorTest.java
 
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidatorTest.java
index 46727b17467..7258771007e 100644
--- 
a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidatorTest.java
+++ 
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/filters/JwtAccessTokenValidatorTest.java
@@ -27,12 +27,15 @@ import org.apache.cxf.jaxrs.ext.MessageContext;
 import org.apache.cxf.message.Message;
 import org.apache.cxf.message.MessageImpl;
 import org.apache.cxf.phase.PhaseInterceptorChain;
+import org.apache.cxf.rs.security.jose.common.JoseConstants;
 import org.apache.cxf.rs.security.jose.jwa.SignatureAlgorithm;
 import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureProvider;
 import org.apache.cxf.rs.security.jose.jws.HmacJwsSignatureVerifier;
+import org.apache.cxf.rs.security.jose.jws.JwsHeaders;
 import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactProducer;
 import org.apache.cxf.rs.security.jose.jwt.JwtClaims;
 import org.apache.cxf.rs.security.jose.jwt.JwtConstants;
+import org.apache.cxf.rs.security.jose.jwt.JwtToken;
 import org.apache.cxf.rs.security.oauth2.common.AccessTokenValidation;
 import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException;
 
@@ -117,6 +120,22 @@ public class JwtAccessTokenValidatorTest {
         assertTrue(ex.getCause().getMessage().contains("cannot be accepted"));
     }
 
+    @Test
+    public void testRejectsJwtThatDeclaresJwtInsteadOfAccessTokenType() {
+        JwtAccessTokenValidator validator = new JwtAccessTokenValidator();
+        validator.setJwsVerifier(new HmacJwsSignatureVerifier(SIGNING_KEY, 
SignatureAlgorithm.HS256));
+        validator.setValidateAudience(false);
+
+        String jwt = createSignedToken(SIGNING_KEY, "signed-client", 3600, 0, 
null, JoseConstants.TYPE_JWT, null);
+        MultivaluedMap<String, String> params = new MultivaluedHashMap<>();
+
+        OAuthServiceException ex = assertThrows(OAuthServiceException.class, 
() ->
+            validator.validateAccessToken(mock(MessageContext.class), 
"Bearer", jwt, params));
+
+        assertNotNull(ex.getCause());
+        assertTrue(ex.getCause().getMessage().contains("Invalid token type"));
+    }
+
     @After
     public void clearCurrentMessage() throws Exception {
         setThreadLocalMessage(null);
@@ -162,11 +181,17 @@ public class JwtAccessTokenValidatorTest {
     }
 
     private static String createSignedToken(String key, String clientId, long 
expiresInSeconds) {
-        return createSignedToken(key, clientId, expiresInSeconds, 0, null);
+        return createSignedToken(key, clientId, expiresInSeconds, 0, null, 
null, null);
     }
 
     private static String createSignedToken(String key, String clientId, long 
expiresInSeconds,
                                            long notBeforeOffsetSeconds, String 
audience) {
+        return createSignedToken(key, clientId, expiresInSeconds, 
notBeforeOffsetSeconds, audience, null, null);
+    }
+
+    private static String createSignedToken(String key, String clientId, long 
expiresInSeconds,
+                                           long notBeforeOffsetSeconds, String 
audience,
+                                           String tokenType, String tokenUse) {
         long now = System.currentTimeMillis() / 1000;
         JwtClaims claims = new JwtClaims();
         claims.setIssuedAt(now);
@@ -174,6 +199,9 @@ public class JwtAccessTokenValidatorTest {
         if (clientId != null) {
             claims.setClaim("client_id", clientId);
         }
+        if (tokenUse != null) {
+            claims.setClaim("token_use", tokenUse);
+        }
         claims.setIssuer("SomeIssuer");
         claims.setSubject("SomeSubject");
         if (notBeforeOffsetSeconds != 0) {
@@ -183,7 +211,12 @@ public class JwtAccessTokenValidatorTest {
             claims.setAudience(audience);
         }
 
-        JwsJwtCompactProducer producer = new JwsJwtCompactProducer(claims);
+        JwsHeaders headers = new JwsHeaders();
+        if (tokenType != null) {
+            headers.setHeader(JoseConstants.HEADER_TYPE, tokenType);
+        }
+
+        JwsJwtCompactProducer producer = new JwsJwtCompactProducer(new 
JwtToken(headers, claims));
         return producer.signWith(new HmacJwsSignatureProvider(key, 
SignatureAlgorithm.HS256));
     }
 

Reply via email to