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

  1. Check the stack trace logged via LoggingUtils.error preceding this exception to distinguish user-denied vs configuration errors.
  2. Have the user retry and approve the prompt within the timeout window.
  3. Verify the Universal Prompt client configuration (client id, client secret, API host) and the CAS callback/redirect URL match the Duo protected application.
  4. 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

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

Related errors


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)