Description
When a client sends a Personal Access Token (PAT) that is either expired or has wrong scopes, the API Mediation Layer produces the same error response with zero diagnostic distinction. A client cannot determine whether to refresh the token or request additional scopes. This creates a poor developer experience and makes automated token lifecycle management impossible.
Current Behaviors (the problems)
Problem 1 — Indistinguishable error header values. Both expired PAT and wrong-scopes PAT produce the exact same X-Zowe-Auth-Failure header value: "org.zowe.apiml.zaas.security.schema.missingAuthentication" (or sometimes "org.zowe.apiml.common.unauthorized"). A client can't tell the difference between "your token expired" and "your token doesn't have the right scopes."
Problem 2 — handleTokenExpire never sets AUTH_FAIL_HEADER. In AuthExceptionHandler.java:216, handleTokenExpire does not set the failure header at all. Compare with handleTokenNotValid (line 199) which does. If ExtractAuthSourceFilter.parse() catches an expired PAT first, the response has no failure header — a silent failure with no diagnostic information.
Problem 3 — /access-token/validate returns empty 401. The validate endpoint returns an empty 401 with zero body and zero diagnostic headers. A client calling /validate to check token status gets no useful information.
Problem 4 — Inconsistent error message selection. Two different methods produce different messages for the same failure scenario:
| Method |
Message |
createMissingAuthenticationErrorMessage() |
"org.zowe.apiml.zaas.security.schema.missingAuthentication" |
createInvalidAuthenticationErrorMessage() |
"org.zowe.apiml.common.unauthorized" |
Which one fires depends on whether isValid() or parse() fails first — an implementation detail that varies between modulith and microservices deployments.
Proposed Solution (4 phases)
Phase 1 — Immediate fixes (lowest effort, highest impact)
AuthExceptionHandler.java: Add AUTH_FAIL_HEADER to handleTokenExpire with a descriptive message indicating token expiry
ReactivePATController.java: Add distinguishing diagnostic information to the /access-token/validate endpoint response (reason header or body)
Phase 2 — Exception specificity
PATAuthSourceService.java: Change isValid() to throw specific exception types instead of returning false:
InvalidScopeException — when token scopes don't match requirements
TokenExpiredException — when token is expired
TokenInvalidatedException — when token has been revoked
Phase 3 — Distinct error messages
ZaasSchemeTransformApi.java: Map different exception types to distinct messages and AUTH_FAIL_HEADER values:
- Expired →
"expired" or "org.zowe.apiml.zaas.security.token.expired"
- Invalid scope →
"invalid_scope" or "org.zowe.apiml.zaas.security.token.invalidScope"
- Revoked →
"revoked" or "org.zowe.apiml.zaas.security.token.revoked"
Phase 4 — Consolidation
ZaasSchemeTransformApi.java: Consolidate the two near-identical error message methods (createMissingAuthenticationErrorMessage and createInvalidAuthenticationErrorMessage) into one, with error-type parameterization.
Acceptance Criteria
- A client receiving a 401 for an expired PAT can distinguish it from a 401 for a wrong-scopes PAT via the
X-Zowe-Auth-Failure header
handleTokenExpire consistently sets AUTH_FAIL_HEADER on all code paths
- The
/access-token/validate endpoint returns diagnostic information (header or body) indicating the reason for token rejection
- The error message is deterministic — the same failure always produces the same header value regardless of modulith vs. microservices deployment
- Existing integration tests pass; new tests cover the distinct failure scenarios
Affected Files
| File |
Change |
zaas-service/src/main/java/org/zowe/apiml/zaas/security/.../AuthExceptionHandler.java |
Add AUTH_FAIL_HEADER to handleTokenExpire |
apiml/src/main/java/org/zowe/apiml/controller/ReactivePATController.java |
Add diagnostic info to /validate endpoint |
zaas-service/src/main/java/org/zowe/apiml/zaas/security/service/schema/source/PATAuthSourceService.java |
Throw specific exceptions from isValid() |
apiml/src/main/java/org/zowe/apiml/ZaasSchemeTransformApi.java |
Map exceptions to distinct messages; consolidate error methods |
Description
When a client sends a Personal Access Token (PAT) that is either expired or has wrong scopes, the API Mediation Layer produces the same error response with zero diagnostic distinction. A client cannot determine whether to refresh the token or request additional scopes. This creates a poor developer experience and makes automated token lifecycle management impossible.
Current Behaviors (the problems)
Problem 1 — Indistinguishable error header values. Both expired PAT and wrong-scopes PAT produce the exact same
X-Zowe-Auth-Failureheader value:"org.zowe.apiml.zaas.security.schema.missingAuthentication"(or sometimes"org.zowe.apiml.common.unauthorized"). A client can't tell the difference between "your token expired" and "your token doesn't have the right scopes."Problem 2 —
handleTokenExpirenever setsAUTH_FAIL_HEADER. InAuthExceptionHandler.java:216,handleTokenExpiredoes not set the failure header at all. Compare withhandleTokenNotValid(line 199) which does. IfExtractAuthSourceFilter.parse()catches an expired PAT first, the response has no failure header — a silent failure with no diagnostic information.Problem 3 —
/access-token/validatereturns empty 401. The validate endpoint returns an empty 401 with zero body and zero diagnostic headers. A client calling/validateto check token status gets no useful information.Problem 4 — Inconsistent error message selection. Two different methods produce different messages for the same failure scenario:
createMissingAuthenticationErrorMessage()"org.zowe.apiml.zaas.security.schema.missingAuthentication"createInvalidAuthenticationErrorMessage()"org.zowe.apiml.common.unauthorized"Which one fires depends on whether
isValid()orparse()fails first — an implementation detail that varies between modulith and microservices deployments.Proposed Solution (4 phases)
Phase 1 — Immediate fixes (lowest effort, highest impact)
AuthExceptionHandler.java: AddAUTH_FAIL_HEADERtohandleTokenExpirewith a descriptive message indicating token expiryReactivePATController.java: Add distinguishing diagnostic information to the/access-token/validateendpoint response (reason header or body)Phase 2 — Exception specificity
PATAuthSourceService.java: ChangeisValid()to throw specific exception types instead of returningfalse:InvalidScopeException— when token scopes don't match requirementsTokenExpiredException— when token is expiredTokenInvalidatedException— when token has been revokedPhase 3 — Distinct error messages
ZaasSchemeTransformApi.java: Map different exception types to distinct messages and AUTH_FAIL_HEADER values:"expired"or"org.zowe.apiml.zaas.security.token.expired""invalid_scope"or"org.zowe.apiml.zaas.security.token.invalidScope""revoked"or"org.zowe.apiml.zaas.security.token.revoked"Phase 4 — Consolidation
ZaasSchemeTransformApi.java: Consolidate the two near-identical error message methods (createMissingAuthenticationErrorMessageandcreateInvalidAuthenticationErrorMessage) into one, with error-type parameterization.Acceptance Criteria
X-Zowe-Auth-FailureheaderhandleTokenExpireconsistently setsAUTH_FAIL_HEADERon all code paths/access-token/validateendpoint returns diagnostic information (header or body) indicating the reason for token rejectionAffected Files
zaas-service/src/main/java/org/zowe/apiml/zaas/security/.../AuthExceptionHandler.javaapiml/src/main/java/org/zowe/apiml/controller/ReactivePATController.javazaas-service/src/main/java/org/zowe/apiml/zaas/security/service/schema/source/PATAuthSourceService.javaapiml/src/main/java/org/zowe/apiml/ZaasSchemeTransformApi.java