apereo/cas · error · AuthenticationException
State [ : : ] does not have a matching transition for
Error message
State [<stateId>:<eventId>:<transitionId>] does not have a matching transition for <eventId>
What it means
MultifactorAuthenticationUtils.validateEventIdForMatchingTransitionInContext verifies that the event about to be signaled in the login webflow has a matching transition defined on the current flow state. When the current state has no transition for that event id, it logs the state/event/transition triple and throws AuthenticationException, aborting the flow rather than leaving the state machine stuck.
Solutions
- Verify the MFA provider id in cas.authn.mfa.* matches the event/transition ids registered in the login webflow.
- Have the user restart the login flow (fresh /login request) instead of retrying a stale page/back-button state.
- If a custom provider, confirm its transition is registered via the webflow customization/execution listener so the state defines that event.
- Clear the bot-stale session/flow state (expire Webflow session storage) and check for load-balanced nodes with mismatched CAS versions.
Example fix
// before: posting event 'mfa-sms' but only google-authenticator is configured // after: align configured provider with the event // application.properties cas.authn.mfa.gauth.core.rank=0 cas.authn.mfa.sms.enabled=true // ensure provider and its flow transition both registered
Defensive patterns
Strategy: try-catch
Try / catch
try {
return MultifactorAuthenticationUtils.validateEventIdForMatchingTransitionInContext(event, ctx);
} catch (AuthenticationException e) {
LOGGER.warn("No matching transition; restarting flow", e);
return null;
} Prevention
- Ensure registered MFA provider ids match webflow transition/event names
- Instruct users to restart login from /login instead of refreshing/back-navigating mid-flow
- Keep webflow state definitions in sync with enabled MFA providers after upgrades
When it happens
Trigger: Calling the utility (or the MFA webflow 'event' action that uses it) with an eventId that the current webflow state cannot transition on — e.g. posting an MFA provider event like 'mfa-gauth' when the state only defines transitions for registered/supported providers, or a stale browser retry after the flow already advanced.
Common situations: MFA provider id misconfigured (event never registered as a transition); user double-submits or uses browser back so the event no longer matches the current state; custom MFA provider registered without wiring its transition into the login flow; CAS upgrade changing flow state ids while a bookmarked URL replays an old event.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Unknown Duo Security authentication attempt
- Failed to authenticate code
- Unauthorized account registration attempt for id
- Failed to authenticate code
- Unauthorized account removal attempt
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/9b5521231eb815ec.
Report an issue: GitHub.
Appendix: source
Thrown at core/cas-server-core-authentication-mfa-api/src/main/java/org/apereo/cas/authentication/MultifactorAuthenticationUtils.java:77
*/
public static Event validateEventIdForMatchingTransitionInContext(final String eventId,
final Optional<RequestContext> context,
final Map<String, Object> attributes) {
val attributesMap = new LocalAttributeMap<>(attributes);
val event = new Event(eventId, eventId, attributesMap);
LOGGER.trace("Attempting to find a matching transition for event id [{}]", event.getId());
return context.map(ctx -> {
LOGGER.trace("Reviewing current state [{}], event [{}] and transition [{}]",
ctx.getCurrentState(), ctx.getCurrentEvent(), ctx.getCurrentTransition());
val def = ctx.getMatchingTransition(event.getId());
if (def == null) {
val msg = String.format("State [%s:%s:%s] does not have a matching transition for %s",
ctx.getCurrentState().getId(),
ctx.getCurrentEvent() != null ? ctx.getCurrentEvent().getId() : "N/A",
ctx.getCurrentTransition() != null ? ctx.getCurrentTransition().getId() : "N/A",
event.getId());
LoggingUtils.error(LOGGER, msg);
throw new AuthenticationException(msg);
}
return event;
}).orElse(event);
}
/**
* Resolve event via multivalued attribute set.
*
* @param principal the principal
* @param attributeValue the attribute value
* @param registeredService the service
* @param service the service
* @param context the context
* @param provider the provider
* @param predicate the predicate
* @return the set
*/View on GitHub (pinned to e7288fc434)