apereo/cas · error · IllegalArgumentException
Password reset token could not be verified to determine…
Error message
Password reset token could not be verified to determine username
What it means
IllegalArgumentException thrown by ValidatePasswordResetTokenAction when the password-reset token extracted from the transient ticket cannot be parsed by passwordManagementService.parseToken(token) into a non-blank username. CAS uses this to confirm which account the reset token belongs to before allowing the password change.
Solutions
- Request a new password-reset email and use the fresh link promptly.
- Ensure cas.authn.pm.reset.crypto.encryption/signing keys are identical on every node in the cluster and have not been rotated since the token was issued.
- Verify the reset link URL was not truncated or re-encoded by the mail client.
- Enable debug logging on PasswordManagementService to see why parseToken returned blank (signature/expiry).
Example fix
// before (mismatched keys across nodes) cas.authn.pm.reset.crypto.signing.key=different-per-node // after # same shared key in every node's properties cas.authn.pm.reset.crypto.signing.key=[shared-secret]
Defensive patterns
Strategy: validation
Validate before calling
// Parse the token yourself before driving the flow
String username = pmService.parseToken(resetToken);
if (StringUtils.isBlank(username)) { return error("reset link is invalid or expired"); } Try / catch
try { return validateAction.execute(ctx); } catch (IllegalArgumentException e) { WebUtils.addErrorMessageToContext(ctx, "screen.pm.reset.invalid"); return errorEvent; } Prevention
- Share identical PM reset-token crypto keys across all cluster nodes.
- Click reset links promptly; treat them as short-lived.
- Avoid URL rewriters that truncate or re-encode the token query parameter.
When it happens
Trigger: A user opens the password-reset flow with a PARAMETER_PASSWORD_RESET_TOKEN whose underlying JWT/token resolves to a blank username: expired token, token signed with a different secret than the one used to create it, malformed token, or a token produced by a different CAS node/ configuration.
Common situations: Reset link clicked after token expiration; cas.authn.pm reset token crypto signing key changed or differs across clustered nodes; link truncated in email; user manually edits the token parameter.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Unable to accept the ID token with an invalid [sub] claim
- Unknown authorization header type
- Token has expired: and is after
- Token cannot be used before
- Unable to verify JWT assertion with any of the configured…
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/794da8016e2ac049.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-pm-webflow/src/main/java/org/apereo/cas/pm/web/flow/actions/ValidatePasswordResetTokenAction.java:42
*/
@RequiredArgsConstructor
@Slf4j
public class ValidatePasswordResetTokenAction extends BaseCasWebflowAction {
private final PasswordManagementService passwordManagementService;
private final TicketRegistry ticketRegistry;
@Override
protected @Nullable Event doExecuteInternal(final RequestContext requestContext) {
try {
val transientTicket = requestContext.getRequestParameters()
.get(PasswordManagementService.PARAMETER_PASSWORD_RESET_TOKEN);
if (StringUtils.isNotBlank(transientTicket)) {
val tst = ticketRegistry.getTicket(transientTicket, TransientSessionTicket.class);
val token = tst.getProperties().get(PasswordManagementService.PARAMETER_TOKEN).toString();
val username = passwordManagementService.parseToken(token);
if (StringUtils.isBlank(username)) {
throw new IllegalArgumentException("Password reset token could not be verified to determine username");
}
return eventFactory.event(this, CasWebflowConstants.TRANSITION_ID_RESET_PASSWORD);
}
val doChange = requestContext.getRequestParameters()
.get(PasswordManagementService.PARAMETER_DO_CHANGE_PASSWORD);
if (StringUtils.isNotBlank(doChange) && BooleanUtils.toBoolean(doChange)) {
return eventFactory.event(this, CasWebflowConstants.TRANSITION_ID_RESET_PASSWORD);
}
return null;
} catch (final Exception e) {
LoggingUtils.warn(LOGGER, e);
return eventFactory.event(this, CasWebflowConstants.TRANSITION_ID_INVALID_PASSWORD_RESET_TOKEN);
}
}
}
View on GitHub (pinned to e7288fc434)