apereo/cas · error · FailedLoginException

Duo Security authentication has failed

Error message

Duo Security authentication has failed

What it means

Duo Security's Authentication API rejected the authentication attempt or the API call failed. DuoSecurityAuthenticationHandler.authenticateDuoApiCredential catches all exceptions (logging them) and then unconditionally throws FailedLoginException, so any Duo API/network/config failure surfaces as this generic failure message. It means CAS could not produce a successful Duo authentication result for the credential.

Solutions

  1. Check the server log just above the exception: LoggingUtils.error(LOGGER, e) records the real root cause (network, signature, or API error) and fix that first
  2. Verify cas.authn.duo.[name].duoIntegrationKey, secretKey and apiHostname match the Duo Admin Console protection values
  3. Confirm the CAS server can reach https://<api-host>.duosecurity.com (curl test; check proxies/firewalls/TLS trust)
  4. Ensure the user is enrolled in the Duo application and the account is not locked/bypass-expired

Example fix

// before (properties)
cas.authn.duo.duo-api-host=api-wrong-host.duosecurity.com
// after
cas.authn.duo.duo-api-host=api-XXXXXXXX.duosecurity.com
Defensive patterns

Strategy: try-catch

Validate before calling

// before calling authentication, verify config
if (duoProps.getApiHostname() == null || duoProps.getSecretKey() == null || duoProps.getDuoIntegrationKey() == null)
    throw new IllegalStateException("Duo integration keys/apiHostname must be configured");

Try / catch

try {
    handlerResult = doAuthentication(credential);
} catch (FailedLoginException e) {
    LOGGER.error("Duo auth failed; inspect earlier LoggingUtils stack trace for root cause", e);
    return redirectToDuoErrorScreen(e);
}

Prevention

When it happens

Trigger: doAuthentication calls the Duo auth API and either the signed response cannot be verified, the Duo service returns an error (invalid integration key/secret, expired license, blocked user), the network call fails/times out, or the returned payload yields no principal — the catch block swallows the cause and rethrows this FailedLoginException.

Common situations: Wrong duoIntegrationKey/secretKey/apiHostname in cas.authn.duo properties; Duo Admin Console not registering the CAS host; firewall/proxy blocking outbound HTTPS to api-*.duosecurity.com; clock skew breaking signed response validation; user not enrolled in Duo.

Understand the failure class

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/2f692a8de4a25044. 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:142

        } 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)