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

coheigea pushed a commit to branch coheigea/JwtAccessTokenValidator
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit a3171a22c9aaab8ddba9b36a9990e1e808ffa5e8
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Wed Jul 8 11:28:24 2026 +0100

    Enforce that Access tokens only are accepted in JwtAccessTokenValidator
---
 .../oauth2/filters/JwtAccessTokenValidator.java    | 17 ++++++++++
 .../filters/JwtAccessTokenValidatorTest.java       | 39 ++++++++++++++++++++--
 2 files changed, 53 insertions(+), 3 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 3f27d739f80..01f333fede1 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
@@ -45,6 +45,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;
@@ -68,6 +71,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);
@@ -76,6 +81,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 e2f96ce609e..327390d3739 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
@@ -26,12 +26,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;
 
@@ -116,6 +119,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);
@@ -161,11 +180,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);
@@ -173,6 +198,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) {
@@ -182,7 +210,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));
     }
 
@@ -197,4 +230,4 @@ public class JwtAccessTokenValidatorTest {
             tl.set(message);
         }
     }
-}
\ No newline at end of file
+}

Reply via email to