apereo/cas · warning

Reaching Duo has failed with error

Error message

Reaching Duo has failed with error: [{}]

What it means

getUserAccount catches any Exception thrown while contacting Duo and logs 'Reaching Duo has failed with error: [{}]' at WARN, then marks the user account status as UNAVAILABLE and caches it. This means CAS could not reach Duo at all (or the request threw), so Duo is treated as down rather than failing authentication outright.

Solutions

  1. Check network connectivity/proxy settings from the CAS host to the Duo api-host (curl the host)
  2. Verify DNS and TLS trust for api-xxxxx.duosecurity.com
  3. Confirm duo api-host, integration key and secret key are correct and current
  4. Clear the cached user account (userAccountCachedMap) or restart after fixing connectivity, since the UNAVAILABLE status is cached
  5. Consider configuring fail-open/fail-closed MFA policy for the outage scenario

Example fix

// before
cas.authn.mfa.duo[0].api-host=api-wrong.duosecurity.com
// after
cas.authn.mfa.duo[0].api-host=api-xxxxxxxx.duosecurity.com
Defensive patterns

Strategy: retry

Validate before calling

// reachability check before auth flow
val host = new URL(duoProperties.getApiHost());
var reachable = InetAddress.getByName(host.getHost()).isReachable(3000);

Try / catch

try { return duo.getUserAccount(username); } catch (Exception e) { LOGGER.warn("Duo unreachable: {}", e.getMessage(), e); return DuoSecurityUserAccountStatus.UNAVAILABLE; /* then retry with backoff */ }

Prevention

When it happens

Trigger: Network failure, DNS failure, TLS error, connection timeout, or any runtime exception during the Duo API HTTP call inside getUserAccount; e.getMessage() of that exception is what appears in the log.

Common situations: Duo API host unreachable from the CAS server (firewall/proxy), invalid api-host DNS name, expired/rotated secret keys causing client errors, or transient network outages; users then see duo unavailable or MFA bypass behavior depending on policy.

Related errors


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

Appendix: source

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

                if (status == DuoSecurityUserAccountStatus.ENROLL) {
                    val enrollUrl = response.get(RESULT_KEY_ENROLL_PORTAL_URL).asString();
                    account.setEnrollPortalUrl(enrollUrl);
                }
            } else {
                val code = result.get(RESULT_KEY_CODE).asInt();
                if (code > RESULT_CODE_ERROR_THRESHOLD) {
                    LOGGER.warn("Duo returned a failure response with code: [{}]. Duo will be considered unavailable",
                        result.get(RESULT_KEY_MESSAGE));
                    throw new DuoSecurityException("Duo returned code %s: %s".formatted(code, result.get(RESULT_KEY_MESSAGE)));
                }
                LOGGER.warn("Duo returned an Invalid response with message [{}] and detail [{}] "
                        + "when determining user account. This maybe a configuration error in the admin request and Duo will "
                        + "still be considered available.",
                    result.hasNonNull(RESULT_KEY_MESSAGE) ? result.get(RESULT_KEY_MESSAGE).asString() : StringUtils.EMPTY,
                    result.hasNonNull(RESULT_KEY_MESSAGE_DETAIL) ? result.get(RESULT_KEY_MESSAGE_DETAIL).asString() : StringUtils.EMPTY);
            }
        } catch (final Exception e) {
            LOGGER.warn("Reaching Duo has failed with error: [{}]", e.getMessage(), e);
            account.setStatus(DuoSecurityUserAccountStatus.UNAVAILABLE);
        }

        userAccountCachedMap.put(account.getUsername(), account);
        LOGGER.debug("Fetched and cached duo user account [{}]", account);
        return account;
    }

    @Override
    public Optional<DuoSecurityAdminApiService> getAdminApiService() {
        if (StringUtils.isNotBlank(properties.getDuoAdminIntegrationKey()) && StringUtils.isNotBlank(properties.getDuoAdminSecretKey())) {
            return Optional.of(new DefaultDuoSecurityAdminApiService(this.httpClient, properties));
        }
        return Optional.empty();
    }

    protected abstract DuoSecurityAuthenticationResult authenticateInternal(Credential credential) throws Exception;

View on GitHub (pinned to e7288fc434)