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
- Inspect the CAS log for the stack trace logged by LoggingUtils.error just before this exception to find the underlying cause.
- Have the user re-enter a current, unused passcode (bypass codes and OTPs are single-use and expire).
- Verify cas.authn.mfa.duo[0].* keys/host point to the same Duo integration the user is enrolled in.
- 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
- Always inspect the LoggingUtils.error stack trace above this exception — the real cause is swallowed.
- Warn users that passcodes/bypass codes are single-use.
- Confirm users are enrolled in the exact Duo integration configured in CAS.
- Test Duo connectivity with /check before blaming user input.
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Duo Security universal prompt authentication has failed
- Invalid response format received from Duo
- Duo returned code
- Unknown Duo Security authentication attempt
- Duo Security authentication has failed
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)