apereo/cas · error · FailedLoginException
Failed: status with message
Error message
Failed: status %s with message: %s
What it means
AzureActiveDirectoryAuthenticationHandler.getUserInfoFromGraph performs a raw HTTP GET against the Microsoft Graph user-info endpoint over HttpURLConnection. When the response status is not 2xx it formats "Failed: status <code> with message: <reason>" and throws FailedLoginException. This is the handler reporting that Microsoft Graph rejected or failed the user-info request during username/password authentication.
Solutions
- Read the logged status code: 401/403 → fix the Azure AD app registration (grant admin consent for the required Graph permissions); 404 → correct the tenant/graph endpoint configuration.
- Verify cas.authn.azuread[0] settings (tenant, client-id, client-secret, base URL) are correct and the credentials are still valid — rotate expired client secrets.
- Test the same Graph call manually (curl with a token from the OAuth2 token endpoint) to confirm the app can reach the user-info endpoint.
- Check conditional-access / MFA policies: the ROPC-style resource-owner credential flow fails for users where Azure requires interactive auth, producing non-2xx here.
- Retry on 429/5xx and monitor Azure AD service health if the status indicates throttling or transient server errors.
Example fix
// before (log: Failed: status 401 with message: Unauthorized) cas.authn.azuread[0].client-id=old-app-id cas.authn.azuread[0].client-secret=expired-secret // after: fix app registration / secret cas.authn.azuread[0].client-id=<correct-app-id> cas.authn.azuread[0].client-secret=<new-secret> # + admin-consented Graph permissions
Defensive patterns
Strategy: try-catch
Validate before calling
// pre-flight: verify Graph reachability and app credentials
HttpURLConnection c = (HttpURLConnection) new URL(graphUserInfoUrl).openConnection();
if (c.getResponseCode() >= 400) LOGGER.warn("Graph endpoint returned {}", c.getResponseCode()); Try / catch
try {
return azureAdHandler.authenticate(credential);
} catch (FailedLoginException e) {
if (e.getMessage().contains("status 4")) {
LOGGER.error("Graph auth/client error — check Azure AD app registration: {}", e.getMessage());
}
throw e;
} Prevention
- Grant and renew admin consent for Graph permissions on the app registration.
- Rotate client secrets before expiry and keep tenant/base-URL settings correct.
- Monitor Graph status codes (429/5xx) and add backoff retry for transient failures.
- Test with curl + a manually obtained token before deploying config changes.
When it happens
Trigger: The conn.getInputStream() path is skipped because HttpStatus.valueOf(httpResponseCode) is not 2xx — e.g. 401/403 (bad/expired access token from getAccessTokenFromUserCredentials), 404 (wrong tenant or graph URL), or 429/5xx from Graph.
Common situations: Wrong tenant name / graph base URL in cas.authn.azuread[0] configuration; client credentials lacking permission to call Graph (admin consent not granted); ROPC flow denied by conditional access policies; network proxy or Azure AD outage producing 5xx.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- Unable to accept response status
- No credentials can be extracted to authenticate the REST…
- Invalid credentials:
- Failed to load configuration metadata
- Authentication handler is disabled
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/6ca43cd6a8840595.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-azuread-authentication/src/main/java/org/apereo/cas/azure/ad/authentication/AzureActiveDirectoryAuthenticationHandler.java:66
}
private String getUserInfoFromGraph(final IAuthenticationResult authenticationResult, final String username) throws Exception {
val url = new URI(Strings.CI.appendIfMissing(properties.getResource(), "/") + "v1.0/users/" + username).toURL();
val conn = (HttpURLConnection) url.openConnection();
try {
conn.setRequestMethod("GET");
conn.setRequestProperty(HttpHeaders.AUTHORIZATION, "Bearer " + authenticationResult.accessToken());
conn.setRequestProperty(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
LOGGER.debug("Fetching user info from [{}] using access token [{}]", url.toExternalForm(), authenticationResult.accessToken());
val httpResponseCode = conn.getResponseCode();
if (HttpStatus.valueOf(httpResponseCode).is2xxSuccessful()) {
try (val in = conn.getInputStream()) {
return IOUtils.toString(in, StandardCharsets.UTF_8);
}
}
val msg = String.format("Failed: status %s with message: %s", httpResponseCode, conn.getResponseMessage());
throw new FailedLoginException(msg);
} finally {
conn.disconnect();
}
}
protected IAuthenticationResult getAccessTokenFromUserCredentials(final String username, final String password) throws Exception {
val clientId = SpringExpressionLanguageValueResolver.getInstance().resolve(properties.getClientId());
val scopes = org.springframework.util.StringUtils.commaDelimitedListToSet(properties.getScope());
if (StringUtils.isNotBlank(properties.getClientSecret())) {
val clientSecret = SpringExpressionLanguageValueResolver.getInstance().resolve(properties.getClientSecret());
val clientCredential = ClientCredentialFactory.createFromSecret(clientSecret);
val context = ConfidentialClientApplication.builder(clientId, clientCredential)
.authority(properties.getLoginUrl())
.validateAuthority(true)
.build();
val resource = Strings.CI.appendIfMissing(properties.getResource(), "/").concat(".default");
val parameters = ClientCredentialParameters.builder(Set.of(resource))
.tenant(properties.getTenant())View on GitHub (pinned to e7288fc434)