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

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

commit 83f05ea15a2a59613df2cbeba4449734f5b85f66
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     | 27 +++++++++++++-----
 .../org/apache/james/jwt/OidcJwtTokenVerifier.java |  5 +++-
 .../apache/james/jwt/OidcJwtTokenVerifierTest.java | 33 ++++++++++++++++++++++
 3 files changed, 57 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 ae197d8e68..f5b79927ec 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
@@ -29,6 +29,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;
@@ -132,16 +133,28 @@ public class JwtTokenVerifier {
         }
     }
 
+    public Optional<Claims> verify(String token) {
+        try {
+            // if the token contains a kid, verify only with the corresponding 
key (or fail)
+            return retrieveClaims(token, kidJwtParser);
+        } catch (NullPointerException npe) { // our own key locator throws NPE 
when there is no kid
+            // if token does not specify kid, fallback to trying all keys
+            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.parseSignedClaims(token);
-            T claim = jws
-                .getPayload()
-                .get(claimName, returnType);
-            if (claim == null) {
-                throw new MalformedJwtException("'" + claimName + "' field in 
token is mandatory");
-            }
-            return Optional.of(claim);
+            return Optional.of(jws.getPayload());
         } catch (JwtException e) { // also if kid was given but our locator 
didn't find the corresponding key
             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 28af294b64..965013a96b 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
@@ -73,7 +73,10 @@ public class OidcJwtTokenVerifier {
     @VisibleForTesting
     Optional<String> verifySignatureAndExtractClaim(String jwtToken) {
         return new 
JwtTokenVerifier(JwksPublicKeyProvider.of(oidcSASLConfiguration.getJwksURL()))
-            .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)));
     }
 
     @VisibleForTesting
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 3755d2fb2d..5480dd53b1 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
@@ -78,6 +78,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