apereo/cas · error · MultifactorAuthenticationFailedException
Credential principal
Error message
Credential principal [{}] does not match authentication principal [{}] What it means
CasSimpleMultifactorAuthenticationHandler.doAuthentication() compares the principal stored on the submitted MFA token credential with the principal of the currently authenticated user. When they differ, it logs this warning and throws MultifactorAuthenticationFailedException, rejecting the token. This prevents using a one-time code issued to a different user.
Solutions
- Request a fresh token code for the currently authenticated user and submit that one
- Ensure each browser session completes its own MFA flow; do not reuse codes across sessions/accounts
- Check ticket registry consistency if tokens appear cross-wired (shared Redis/Hazelcast keys across environments)
- Verify custom principal resolution in the MFA service does not alter the principal identity between issuance and validation
Defensive patterns
Strategy: try-catch
Validate before calling
// Before submitting, confirm the code belongs to the current session/user
if (!mfaContext.getTokenIdFor(principal.getId()).equals(submittedTokenId)) {
throw new IllegalStateException("Token does not belong to current user");
} Try / catch
try {
handlerResult = handler.authenticate(credential, service);
} catch (MultifactorAuthenticationFailedException e) {
// prompt user to request a new code
return mfaFlow.requestNewToken(principal);
} Prevention
- Never reuse MFA codes across accounts or browser sessions
- Complete each MFA flow in the session that initiated it
- Monitor for repeated mismatches as a possible token-confusion or replay attack
When it happens
Trigger: Submitting a simple-MFA token code that was issued for another principal (or whose stored principal was mutated) during the multifactor authentication step of a different user's session.
Common situations: User pastes a code from a colleague's or another tab's email/SMS; ticket-registry data corruption or shared token IDs across environments; session switch where the browser retains another account's pending code.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Principal assigned to token
- Failed to authenticate code
- Unable to extract credentials for multifactor authentication
- Duo Security authentication has failed
- Cannot validate authentication for: [login]
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/125d6a75466a86e8.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-simple-mfa-core/src/main/java/org/apereo/cas/mfa/simple/CasSimpleMultifactorAuthenticationHandler.java:73
}
@Override
public boolean supports(final Class<? extends Credential> clazz) {
return CasSimpleMultifactorTokenCredential.class.isAssignableFrom(clazz);
}
@Override
protected AuthenticationHandlerExecutionResult doAuthentication(final Credential credential,
final Service service) throws Exception {
return FunctionUtils.doAndThrow(() -> {
val tokenCredential = (CasSimpleMultifactorTokenCredential) credential;
val credentialPrincipal = multifactorAuthenticationService.fetch(tokenCredential);
val resolvedPrincipal = resolvePrincipal(applicationContext, credentialPrincipal);
val principal = multifactorAuthenticationService.validate(resolvedPrincipal, tokenCredential);
val activePrincipal = findActivePrincipal();
if (!principal.equals(activePrincipal)) {
LOGGER.warn("Credential principal [{}] does not match authentication principal [{}]",
principal.getId(), activePrincipal.getId());
throw new MultifactorAuthenticationFailedException("Failed to authenticate code " + tokenCredential.getId());
}
return createHandlerResult(tokenCredential, principal);
}, MultifactorAuthenticationFailedException::new);
}
protected Principal findActivePrincipal() {
val authentication = Objects.requireNonNull(WebUtils.getInProgressAuthentication());
val principal = authentication.getPrincipal();
return principal.getOwner();
}
}
View on GitHub (pinned to e7288fc434)