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

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

commit a2a604a4ffeef1b21a5ddda95f82059da33dafd6
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Wed Jul 29 11:24:18 2026 +0100

    Make sure the refresh token client matches that of the authenticated client 
(#3346)
    
    (cherry picked from commit c95fb6366930ce16c32481f230a5afa89489bd25)
---
 .../oauth2/provider/AbstractOAuthDataProvider.java |  3 ++
 .../provider/AbstractOAuthDataProviderTest.java    | 32 ++++++++++++++++++++++
 2 files changed, 35 insertions(+)

diff --git 
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProvider.java
 
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProvider.java
index 8a35996a968..d425d3c8fbe 100644
--- 
a/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProvider.java
+++ 
b/rt/rs/security/oauth-parent/oauth2/src/main/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProvider.java
@@ -240,6 +240,9 @@ public abstract class AbstractOAuthDataProvider implements 
OAuthDataProvider, Cl
         if (currentRefreshToken == null) {
             throw new OAuthServiceException(OAuthConstants.ACCESS_DENIED);
         }
+        if 
(!currentRefreshToken.getClient().getClientId().equals(client.getClientId())) {
+            throw new OAuthServiceException(OAuthConstants.INVALID_GRANT);
+        }
         if (OAuthUtils.isExpired(currentRefreshToken.getIssuedAt(), 
currentRefreshToken.getExpiresIn())) {
             if (!recycleRefreshTokens) {
                 revokeRefreshToken(client, refreshTokenKey);
diff --git 
a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProviderTest.java
 
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProviderTest.java
index 0d160d352cd..782cad88b07 100644
--- 
a/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProviderTest.java
+++ 
b/rt/rs/security/oauth-parent/oauth2/src/test/java/org/apache/cxf/rs/security/oauth2/provider/AbstractOAuthDataProviderTest.java
@@ -346,6 +346,38 @@ abstract class AbstractOAuthDataProviderTest {
         assertNull(getProvider().getRefreshToken(rt.getTokenKey()));
     }
 
+    /**
+     * Regression test for cross-client refresh token acceptance when 
recycleRefreshTokens=false.
+     * Client B must not be able to exchange Client A's refresh token for an 
access token.
+     */
+    @Test
+    public void testCrossClientRefreshTokenRejectedWhenRecycleDisabled() {
+        getProvider().setRecycleRefreshTokens(false);
+
+        // Client A obtains a refresh token for its resource owner.
+        Client clientA = addClient("101", "alice");
+        AccessTokenRegistration atrA = new AccessTokenRegistration();
+        atrA.setClient(clientA);
+        atrA.setApprovedScope(Arrays.asList("a", "refreshToken"));
+        atrA.setSubject(clientA.getResourceOwnerSubject());
+        ServerAccessToken atA = getProvider().createAccessToken(atrA);
+        assertNotNull(atA.getRefreshToken());
+
+        // Client B is a separate registered client controlled by an attacker.
+        Client clientB = addClient("102", "bob");
+
+        // Attacker uses Client B credentials to present Client A's refresh 
token.
+        try {
+            getProvider().refreshAccessToken(clientB, atA.getRefreshToken(), 
Collections.emptyList());
+            fail("Cross-client refresh token use must be rejected with 
OAuthServiceException");
+        } catch (OAuthServiceException ex) {
+            assertEquals(OAuthConstants.INVALID_GRANT, ex.getMessage());
+        }
+
+        // Client A's original access token must remain usable.
+        assertNotNull(getProvider().getAccessToken(atA.getTokenKey()));
+    }
+
     protected Client addClient(String clientId, String userLogin) {
         Client c = new Client();
         c.setRedirectUris(Collections.singletonList("http://client/redirect";));

Reply via email to