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

mridulpathak pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 2dd1519560 Fixed: JWT refresh tokens no longer accepted as access 
tokens (OFBIZ-13493) (#1664)
2dd1519560 is described below

commit 2dd1519560eebb2688ef8de8674fb06903b1afc0
Author: Mridul Pathak <[email protected]>
AuthorDate: Fri Aug 21 15:30:09 2026 +0530

    Fixed: JWT refresh tokens no longer accepted as access tokens (OFBIZ-13493) 
(#1664)
    
    createRefreshToken() signs refresh tokens with the same key as access 
tokens and a much longer expiration (24h vs 30min by default), but 
validateToken() never checked the "type" claim, so checkJWTLogin(), 
TokenFilter, and the REST API's APIAuthFilter all accepted a refresh token 
presented directly as a Bearer access credential, extending a leaked refresh 
token's usable window well beyond what the access-token lifetime is meant to 
allow. JWTManager.validateAccessToken() now rejects tok [...]
---
 .../ofbiz/ws/rs/security/auth/APIAuthFilter.java     |  2 +-
 .../org/apache/ofbiz/webapp/control/JWTManager.java  | 20 ++++++++++++++++++--
 .../org/apache/ofbiz/webapp/control/TokenFilter.java |  2 +-
 3 files changed, 20 insertions(+), 4 deletions(-)

diff --git 
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/security/auth/APIAuthFilter.java
 
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/security/auth/APIAuthFilter.java
index d106f83014..abbaefafb3 100644
--- 
a/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/security/auth/APIAuthFilter.java
+++ 
b/framework/rest-api/src/main/java/org/apache/ofbiz/ws/rs/security/auth/APIAuthFilter.java
@@ -80,7 +80,7 @@ public class APIAuthFilter implements ContainerRequestFilter {
             return;
         }
         String jwtToken = JWTManager.getHeaderAuthBearerToken(httpRequest);
-        Map<String, Object> claims = JWTManager.validateToken(delegator, 
jwtToken);
+        Map<String, Object> claims = JWTManager.validateAccessToken(delegator, 
jwtToken);
         if (claims.containsKey(ModelService.ERROR_MESSAGE)) {
             abortWithUnauthorized(requestContext, true, "Unauthorized: " + 
(String) claims.get(ModelService.ERROR_MESSAGE));
         } else {
diff --git 
a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/JWTManager.java
 
b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/JWTManager.java
index 08ed1cf196..2a61465f8f 100644
--- 
a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/JWTManager.java
+++ 
b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/JWTManager.java
@@ -467,14 +467,14 @@ public class JWTManager {
     }
 
     /**
-     * Validate the token usingJWTManager::validateToken
+     * Validate the token usingJWTManager::validateAccessToken
      * If it fails, returns a ModelService.ERROR_MESSAGE in the result
      * @param jwtToken The JWT which normally contains the userLoginId
      * @param key the secret key to decrypt the token
      * @return Map of name, value pairs composing the result
      */
     private static Map<String, Object> validateJwtToken(Delegator delegator, 
String jwtToken) {
-        Map<String, Object> result = validateToken(delegator, jwtToken);
+        Map<String, Object> result = validateAccessToken(delegator, jwtToken);
         if (result.containsKey(ModelService.ERROR_MESSAGE)) {
             // Something unexpected happened here
             Debug.logWarning("There was a problem with the JWT token, no 
single sign on user login possible.", MODULE);
@@ -495,4 +495,20 @@ public class JWTManager {
         }
         return claims;
     }
+
+    /**
+     * Validates a JWT for use as an access credential, rejecting it if it is 
a refresh token.
+     * Refresh tokens are signed with the same key as access tokens but carry 
a much longer
+     * expiration, so they must never be accepted outside the dedicated 
refresh flow.
+     * @param delegator the delegator
+     * @param jwtToken the JWT to validate
+     * @return the token claims if it is a valid, non-refresh token, or an 
error entry otherwise
+     */
+    public static Map<String, Object> validateAccessToken(Delegator delegator, 
String jwtToken) {
+        Map<String, Object> claims = validateToken(delegator, jwtToken);
+        if (!claims.containsKey(ModelService.ERROR_MESSAGE) && 
"refresh".equals(claims.get("type"))) {
+            return ServiceUtil.returnError("Invalid access token.");
+        }
+        return claims;
+    }
 }
diff --git 
a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/TokenFilter.java
 
b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/TokenFilter.java
index 7d4354b32e..1782aa28d3 100644
--- 
a/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/TokenFilter.java
+++ 
b/framework/webapp/src/main/java/org/apache/ofbiz/webapp/control/TokenFilter.java
@@ -63,7 +63,7 @@ public class TokenFilter implements Filter {
         String token = JWTManager.getHeaderAuthBearerToken(httpRequest);
 
         if (UtilValidate.isNotEmpty(token)) {
-            Map<String, Object> result = JWTManager.validateToken(delegator, 
token);
+            Map<String, Object> result = 
JWTManager.validateAccessToken(delegator, token);
             String userLoginId = (String) result.get("userLoginId");
             if 
(UtilValidate.isNotEmpty(result.get(ModelService.ERROR_MESSAGE))) {
                 httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

Reply via email to