apereo/cas · error · FailedLoginException
Duo Security universal prompt authentication has failed
Error message
Duo Security universal prompt authentication has failed
What it means
FailedLoginException thrown by authenticateDuoUniversalPromptCredential() when authentication via the Duo Universal Prompt (secondary authentication call with a signed Duo response) fails or throws. Like the passcode path, all Throwables are logged via LoggingUtils.error and rethrown as this generic FailedLoginException.
Solutions
- Check the stack trace logged via LoggingUtils.error preceding this exception to distinguish user-denied vs configuration errors.
- Have the user retry and approve the prompt within the timeout window.
- Verify the Universal Prompt client configuration (client id, client secret, API host) and the CAS callback/redirect URL match the Duo protected application.
- Confirm the Duo application in the Duo Admin console uses the Universal Prompt and is in an active, approved state.
Example fix
// before: mismatched Duo OIDC redirect cas.authn.mfa.duo[0].redirect-uri=https://cas.example.org/cas/wrong/callback // after cas.authn.mfa.duo[0].redirect-uri=https://cas.example.org/cas/login?client_id=... (registered Duo redirect)
Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-validate Universal Prompt config at startup
class DuoPromptPrecheck {
void check(DuoSecurityDuoAdminProperties p) {
if (p.getDuoApiHost() == null || p.getClientId() == null || p.getClientSecret() == null)
throw new IllegalStateException("Universal Prompt client config incomplete");
}
} Type guard
boolean isUniversalPromptCredential(Credential c) {
return c instanceof DuoSecurityUniversalPromptCredential;
} Try / catch
try {
result = duoHandler.authenticate(promptCredential);
} catch (FailedLoginException e) {
// differentiate: user-denied (retry) vs config error (alert admin)
LOGGER.error("Universal Prompt auth failed; see prior logged cause", e);
} Prevention
- Check the LoggingUtils.error stack trace to separate user-denied results from configuration failures.
- Register the exact CAS redirect URI in the Duo Universal Prompt application.
- Keep Duo transaction timeouts in mind; instruct users to approve promptly.
- Avoid CAS restarts mid-flow for stateless verification of in-flight Duo transactions.
When it happens
Trigger: doAuthentication with a DuoSecurityUniversalPromptCredential whose Duo-issued signed response cannot be verified or whose authentication result is not approved — expired/failed Duo transaction, user denied the push, state parameter mismatch, or any exception calling the Duo service.
Common situations: User denied or ignored the Duo push; Duo transaction timed out before approval; misconfigured redirect URI / client id / client secret for Universal Prompt (OIDC-based) causing response validation failure; CAS restart losing in-flight Duo transaction state; clock skew affecting token validation.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Duo Security passcode 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/032542ff84fe6562.
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:127
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");
}
private AuthenticationHandlerExecutionResult authenticateDuoApiCredential(
final DuoSecurityDirectCredential credential) throws FailedLoginException {
try {
val duoAuthenticationService = multifactorAuthenticationProvider.getObject().getDuoAuthenticationService();
if (duoAuthenticationService.authenticate(credential).isSuccess()) {
val principal = resolvePrincipal(credential.getPrincipal());
LOGGER.debug("Duo Security has successfully authenticated [{}]", principal.getId());
return createHandlerResult(credential, principal, new ArrayList<>());
}
} catch (final Exception e) {
LoggingUtils.error(LOGGER, e);
}
throw new FailedLoginException("Duo Security authentication has failed");
}
}
View on GitHub (pinned to e7288fc434)