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

chibenwa pushed a commit to branch 3.8.x
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 7687eda5c4fd8a3977a962b1dd1909978ae99538
Author: Benoit TELLIER <[email protected]>
AuthorDate: Fri Jan 16 17:52:36 2026 +0100

    [ENHANCEMENT] Validate aud without introspect
---
 .../org/apache/james/jwt/JwtTokenVerifier.java     | 21 +++++++++-----
 .../org/apache/james/jwt/OidcJwtTokenVerifier.java |  5 +++-
 .../apache/james/jwt/OidcJwtTokenVerifierTest.java | 33 ++++++++++++++++++++++
 3 files changed, 51 insertions(+), 8 deletions(-)

diff --git 
a/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java 
b/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java
index c5bc4ff9b8..66daee29a2 100644
--- 
a/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java
+++ 
b/server/protocols/jwt/src/main/java/org/apache/james/jwt/JwtTokenVerifier.java
@@ -25,6 +25,7 @@ import java.util.Optional;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.github.fge.lambdas.Throwing;
 import com.google.common.collect.ImmutableList;
 
 import io.jsonwebtoken.Claims;
@@ -67,16 +68,22 @@ public class JwtTokenVerifier {
             .findFirst();
     }
 
+    public Optional<Claims> verify(String token) {
+        return jwtParsers.stream()
+            .flatMap(parser -> retrieveClaims(token, parser).stream())
+            .findFirst();
+    }
+
     private <T> Optional<T> verifyAndExtractClaim(String token, String 
claimName, Class<T> returnType, JwtParser parser) {
+        return retrieveClaims(token, parser)
+            .map(Throwing.function(claims -> 
Optional.ofNullable(claims.get(claimName, returnType))
+                .orElseThrow(() -> new MalformedJwtException("'" + claimName + 
"' field in token is mandatory"))));
+    }
+
+    private Optional<Claims> retrieveClaims(String token, JwtParser parser) {
         try {
             Jws<Claims> jws = parser.parseClaimsJws(token);
-            T claim = jws
-                .getBody()
-                .get(claimName, returnType);
-            if (claim == null) {
-                throw new MalformedJwtException("'" + claimName + "' field in 
token is mandatory");
-            }
-            return Optional.of(claim);
+            return Optional.of(jws.getBody());
         } catch (JwtException e) {
             LOGGER.info("Failed Jwt verification", e);
             return Optional.empty();
diff --git 
a/server/protocols/jwt/src/main/java/org/apache/james/jwt/OidcJwtTokenVerifier.java
 
b/server/protocols/jwt/src/main/java/org/apache/james/jwt/OidcJwtTokenVerifier.java
index 6f0be04e56..b1ca932fe8 100644
--- 
a/server/protocols/jwt/src/main/java/org/apache/james/jwt/OidcJwtTokenVerifier.java
+++ 
b/server/protocols/jwt/src/main/java/org/apache/james/jwt/OidcJwtTokenVerifier.java
@@ -82,7 +82,10 @@ public class OidcJwtTokenVerifier {
                 .map(kidValue -> 
JwksPublicKeyProvider.of(oidcSASLConfiguration.getJwksURL(), kidValue))
                 
.orElse(JwksPublicKeyProvider.of(oidcSASLConfiguration.getJwksURL()));
         return new JwtTokenVerifier(jwksPublicKeyProvider)
-            .verifyAndExtractClaim(jwtToken, oidcSASLConfiguration.getClaim(), 
String.class);
+            .verify(jwtToken)
+            .filter(claims -> oidcSASLConfiguration.getAud().map(expectedAud 
-> claims.getAudience().contains(expectedAud))
+                .orElse(true)) // true if no aud is configured
+            .flatMap(claims -> 
Optional.ofNullable(claims.get(oidcSASLConfiguration.getClaim(), 
String.class)));
     }
 
     private <T> Optional<T> getClaimWithoutSignatureVerification(String token, 
String claimName) {
diff --git 
a/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcJwtTokenVerifierTest.java
 
b/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcJwtTokenVerifierTest.java
index 0aee8af43c..1e1e09a33b 100644
--- 
a/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcJwtTokenVerifierTest.java
+++ 
b/server/protocols/jwt/src/test/java/org/apache/james/jwt/OidcJwtTokenVerifierTest.java
@@ -68,6 +68,39 @@ class OidcJwtTokenVerifierTest {
         }
     }
 
+    @Test
+    void verifyAndClaimShouldAcceptValidAud() throws Exception {
+        Optional<String> emailAddress = new OidcJwtTokenVerifier(
+            OidcSASLConfiguration.builder()
+                .jwksURL(getJwksURL())
+                .scope("email")
+                .oidcConfigurationURL(new URL("https://whatever.nte";))
+                .claim("email_address")
+                .aud("account")
+                .build())
+            .verifySignatureAndExtractClaim(OidcTokenFixture.VALID_TOKEN);
+
+        SoftAssertions.assertSoftly(softly -> {
+            softly.assertThat(emailAddress.isPresent()).isTrue();
+            softly.assertThat(emailAddress.get()).isEqualTo("[email protected]");
+        });
+    }
+
+    @Test
+    void verifyAndClaimShouldRejectInvalidAud() throws Exception {
+        Optional<String> emailAddress = new OidcJwtTokenVerifier(
+            OidcSASLConfiguration.builder()
+                .jwksURL(getJwksURL())
+                .scope("email")
+                .oidcConfigurationURL(new URL("https://whatever.nte";))
+                .claim("email_address")
+                .aud("other")
+                .build())
+            .verifySignatureAndExtractClaim(OidcTokenFixture.VALID_TOKEN);
+
+       assertThat(emailAddress).isEmpty();
+    }
+
     @Test
     void verifyAndClaimShouldReturnClaimValueWhenValidTokenHasKid() {
         Optional<String> emailAddress = new 
OidcJwtTokenVerifier(configForClaim("email_address")).verifySignatureAndExtractClaim(OidcTokenFixture.VALID_TOKEN);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to