apereo/cas · error · FailedLoginException
Invalid credentials:
Error message
Invalid credentials:
What it means
Catch-all in AzureActiveDirectoryAuthenticationHandler.authenticateUsernamePasswordInternal: any exception during the Azure AD username/password flow (token acquisition via getAccessTokenFromUserCredentials, Graph user-info fetch, or principal attribute mapping) is logged and rethrown as FailedLoginException("Invalid credentials: " + e.getMessage()). The original exception's message is preserved but its type is flattened to a failed login.
Solutions
- Read the nested cause logged by LoggingUtils.error (the message appended after 'Invalid credentials:') to identify the real failure — e.g. invalid_grant vs. connection timeout.
- If invalid_grant/MFA required: the ROPC credential flow cannot proceed for that user — have the user authenticate interactively or exempt them from conditional access, since this handler only supports direct username/password.
- Validate cas.authn.azuread[0] client-id, client-secret, tenant and that the secret has not expired in the Azure portal; rotate and retry.
- Verify the supplied username/password is correct and the account is not locked/expired in Azure AD.
- Check network/DNS/proxy reachability to login.microsoftonline.com and graph.microsoft.com from the CAS server.
Example fix
// before: users with conditional-access MFA always fail // Invalid credentials: invalid_grant - MFA required // after: exclude these users from conditional access or use an // interactive OAuth flow instead of the ROPC-based handler
Defensive patterns
Strategy: try-catch
Validate before calling
// validate config before authenticating
if (StringUtils.isAnyBlank(properties.getClientId(), properties.getClientSecret(), properties.getTenant())) {
throw new IllegalArgumentException("azuread client-id/secret/tenant must be set");
} Try / catch
try {
return handler.authenticateUsernamePasswordInternal(credential);
} catch (FailedLoginException e) {
// 'Invalid credentials: <cause message>' — inspect the appended cause
LOGGER.error("Azure AD auth failed, root cause: {}", e.getMessage());
throw e;
} Prevention
- Always log/inspect the wrapped cause message — the real error (invalid_grant, timeout, 401) is appended after 'Invalid credentials:'.
- Verify the account can use the ROPC flow (no MFA/conditional-access requirements) before onboarding users.
- Rotate Azure AD client secrets proactively and validate tenant/client-id on each deployment.
- Add a health check that acquires a token at startup to catch credential/config issues early.
When it happens
Trigger: Any Exception thrown inside authenticateUsernamePasswordInternal — MSAL token acquisition failures (invalid_grant, wrong client secret, network errors), the Graph HTTP failures from getUserInfoFromGraph, JSON/attribute parsing errors, or NPEs from unexpected responses.
Common situations: Users whose accounts require MFA/conditional access (ROPC unsupported → invalid_grant); expired Azure AD client secret; wrong tenant/client-id; password wrong or expired in Azure AD; transient network failure to login.microsoftonline.com.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Failed: status with message
- Unable to accept cookie for authentication
- Cannot validate authentication for: [login]
- Invalid credentials:
- Could not authenticate account for
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/9dfe071a5a56e738.
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:126
LOGGER.trace("Fetching token for [{}]", username);
val result = getAccessTokenFromUserCredentials(username, credential.toPassword());
LOGGER.debug("Retrieved token [{}] for [{}]", result.accessToken(), username);
val userInfo = getUserInfoFromGraph(result, username);
LOGGER.trace("Retrieved user info [{}]", userInfo);
val userInfoMap = (Map<String, ?>) MAPPER.readValue(JsonValue.readHjson(userInfo).toString(), Map.class);
val attributeMap = new HashMap<String, List<Object>>(userInfoMap.size());
userInfoMap.forEach((key, value) -> {
val values = CollectionUtils.toCollection(value, ArrayList.class);
if (!values.isEmpty()) {
attributeMap.put(key, values);
}
});
val principal = principalFactory.createPrincipal(username, attributeMap);
LOGGER.debug("Created principal for id [{}] and [{}] attributes", username, attributeMap);
return createHandlerResult(credential, principal, new ArrayList<>());
} catch (final Exception e) {
LoggingUtils.error(LOGGER, e);
throw new FailedLoginException("Invalid credentials: " + e.getMessage());
}
}
}
View on GitHub (pinned to e7288fc434)