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

  1. Request a new password-reset email and use the fresh link promptly.
  2. 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.
  3. Verify the reset link URL was not truncated or re-encoded by the mail client.
  4. 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

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


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)