apereo/cas · error · FailedLoginException

Failed to authenticate code

Error message

Failed to authenticate code 

What it means

BaseCasSimpleMultifactorAuthenticationService.validateTokenForPrincipal throws FailedLoginException "Failed to authenticate code <id>" when the stored MFA authentication ticket has no PROPERTY_PRINCIPAL entry. Without the principal recorded on the token, the service cannot confirm the code belongs to the presenting user, so it deletes the token and fails the attempt.

Solutions

  1. Regenerate the MFA token through the standard flow so the principal property is set
  2. Inspect the ticket registry record for the token and confirm PROPERTY_PRINCIPAL exists
  3. Upgrade/align all CAS nodes to the same version so ticket serialization is consistent
  4. Clear stale/corrupted tokens from the registry and have the user restart the MFA step
Defensive patterns

Strategy: retry

Validate before calling

// verify the token record actually carries the principal property before validating
var acct = (CasSimpleMultifactorAuthenticationTicket) ticketRegistry.getTicket(tokenId);
if (acct == null || !acct.getProperties().containsKey(
        CasSimpleMultifactorAuthenticationConstants.PROPERTY_PRINCIPAL)) {
    throw new IllegalStateException("Token missing principal property; request a new code");
}

Try / catch

try {
    principal = mfaService.validate(resolvedPrincipal, credential);
} catch (FailedLoginException e) {
    LOGGER.warn("MFA token invalid ({}); restarting MFA with a fresh code", e.getMessage());
    restartMfaFlow(resolvedPrincipal);
}

Prevention

When it happens

Trigger: validate -> validateTokenForPrincipal when acct.getProperties() lacks CasSimpleMultifactorAuthenticationConstants.PROPERTY_PRINCIPAL — i.e., the token was created without the principal property, was corrupted, or is a fabricated/foreign ticket ID.

Common situations: A token was minted by a path that did not attach the principal property (custom/REST flows); ticket-registry data loss or serialization issues stripping properties; attacker probing with guessed token IDs; version-upgrade migration where older tokens lack the property.

Understand the failure class

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/687801307e5628fd. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-simple-mfa-core/src/main/java/org/apereo/cas/mfa/simple/validation/BaseCasSimpleMultifactorAuthenticationService.java:37

 * @since 7.2.0
 */
@Slf4j
@RequiredArgsConstructor
public abstract class BaseCasSimpleMultifactorAuthenticationService implements CasSimpleMultifactorAuthenticationService {
    protected final TicketRegistry ticketRegistry;

    @Override
    public CasSimpleMultifactorAuthenticationTicket getMultifactorAuthenticationTicket(final CasSimpleMultifactorTokenCredential credential) {
        val tokenId = normalize(credential.getId());
        return ticketRegistry.getTicket(tokenId, CasSimpleMultifactorAuthenticationTicket.class);
    }

    protected Principal validateTokenForPrincipal(final Principal resolvedPrincipal, final CasSimpleMultifactorAuthenticationTicket acct)
        throws FailedLoginException {
        if (!acct.getProperties().containsKey(CasSimpleMultifactorAuthenticationConstants.PROPERTY_PRINCIPAL)) {
            LOGGER.warn("Unable to locate principal for token [{}]", acct.getId());
            deleteToken(acct);
            throw new FailedLoginException("Failed to authenticate code " + acct.getId());
        }
        val principal = (Principal) acct.getProperties().get(CasSimpleMultifactorAuthenticationConstants.PROPERTY_PRINCIPAL);
        if (!principal.equals(resolvedPrincipal)) {
            LOGGER.warn("Principal assigned to token [{}] is unauthorized for token [{}]", principal.getId(), acct.getId());
            deleteToken(acct);
            throw new FailedLoginException("Failed to authenticate code " + acct.getId());
        }
        return principal;
    }

    protected static String normalize(final String tokenId) {
        if (!tokenId.startsWith(CasSimpleMultifactorAuthenticationTicket.PREFIX)) {
            return CasSimpleMultifactorAuthenticationTicket.PREFIX + UniqueTicketIdGenerator.SEPARATOR + tokenId;
        }
        return tokenId;
    }

    protected void deleteToken(final CasSimpleMultifactorAuthenticationTicket acct) {

View on GitHub (pinned to e7288fc434)