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);