apereo/cas · error · FailedLoginException

Duo Security passcode authentication has failed

Error message

Duo Security passcode authentication has failed

What it means

FailedLoginException thrown by authenticateDuoApiCredential()'s passcode branch (authenticateDuoPasscodeCredential) when validating a Duo passcode credential fails for any reason. Any Throwable raised while verifying the passcode with the Duo service is logged via LoggingUtils.error, swallowed, and converted into this generic FailedLoginException.

Solutions

  1. Inspect the CAS log for the stack trace logged by LoggingUtils.error just before this exception to find the underlying cause.
  2. Have the user re-enter a current, unused passcode (bypass codes and OTPs are single-use and expire).
  3. Verify cas.authn.mfa.duo[0].* keys/host point to the same Duo integration the user is enrolled in.
  4. Test the Duo integration connectivity (the /check endpoint) to rule out network or clock-skew issues.

Example fix

// before: reused passcode
DuoSecurityPasscodeCredential(id, "123456789")  // already consumed
// after: fresh passcode from Duo Mobile or a new bypass code
DuoSecurityPasscodeCredential(id, freshlyGeneratedPasscode)
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate passcode format before submission (Duo passcodes are numeric)
if (passcode == null || !passcode.matches("\\d{6,10}")) {
    throw new IllegalArgumentException("Passcode format invalid");
}

Type guard

boolean isPasscodeCredential(Credential c) {
    return c instanceof DuoSecurityPasscodeCredential;
}

Try / catch

try {
    result = duoHandler.authenticate(passcodeCredential);
} catch (FailedLoginException e) {
    // check CAS logs for the swallowed root cause, then prompt user for a fresh passcode
    promptUserForNewPasscode();
}

Prevention

When it happens

Trigger: Submitting DuoSecurityPasscodeCredential through doAuthentication when the passcode verification call fails: wrong/expired passcode, Duo service unreachable, Duo API returning FAIL, or any exception in the verification path.

Common situations: User typing an old or already-used bypass code; user enrolled in a different Duo integration than the one CAS is configured against; Duo clock/signature problems; network outage between CAS and Duo. Because the cause is swallowed, the real reason only appears in the logged stack trace.

Understand the failure class

Related errors


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

Appendix: source

Thrown at support/cas-server-support-duo-core/src/main/java/org/apereo/cas/adaptors/duo/authn/DuoSecurityAuthenticationHandler.java:111

            .stream()
            .filter(resolver -> resolver.supports(principal))
            .findFirst()
            .map(resolver -> resolver.resolve(principal))
            .orElseThrow(() -> new IllegalStateException("Unable to resolve principal for Duo Security multifactor authentication"));
    }

    private AuthenticationHandlerExecutionResult authenticateDuoPasscodeCredential(
        final DuoSecurityPasscodeCredential credential) throws Exception {
        try {
            val duoAuthenticationService = multifactorAuthenticationProvider.getObject().getDuoAuthenticationService();
            if (duoAuthenticationService.authenticate(credential).isSuccess()) {
                val principal = principalFactory.createPrincipal(credential.getId());
                return createHandlerResult(credential, principal, new ArrayList<>());
            }
        } catch (final Throwable e) {
            LoggingUtils.error(LOGGER, e);
        }
        throw new FailedLoginException("Duo Security passcode authentication has failed");
    }

    private AuthenticationHandlerExecutionResult authenticateDuoUniversalPromptCredential(
        final DuoSecurityUniversalPromptCredential credential) throws Exception {
        try {
            val duoAuthenticationService = multifactorAuthenticationProvider.getObject().getDuoAuthenticationService();
            val result = duoAuthenticationService.authenticate(credential);
            if (result.isSuccess()) {
                val principal = principalFactory.createPrincipal(result.getUsername(), result.getAttributes());
                LOGGER.debug("Duo Security Universal Prompt has successfully authenticated [{}]", Objects.requireNonNull(principal).getId());
                return createHandlerResult(credential, principal, new ArrayList<>());
            }
        } catch (final Throwable e) {
            LoggingUtils.error(LOGGER, e);
        }
        throw new FailedLoginException("Duo Security universal prompt authentication has failed");
    }

View on GitHub (pinned to e7288fc434)