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
- Check network connectivity/proxy settings from the CAS host to the Duo api-host (curl the host)
- Verify DNS and TLS trust for api-xxxxx.duosecurity.com
- Confirm duo api-host, integration key and secret key are correct and current
- Clear the cached user account (userAccountCachedMap) or restart after fixing connectivity, since the UNAVAILABLE status is cached
- 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
- Open firewall/proxy egress to Duo api-host
- Monitor CAS-to-Duo connectivity
- Remember UNAVAILABLE is cached — clear/restart after fixing network
- Configure MFA fail-open/closed policy deliberately
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
- Invalid response format received from Duo
- Duo returned code
- Unknown Duo Security authentication attempt
- Duo Security passcode authentication has failed
- Duo Security universal prompt authentication has failed
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)