apereo/cas · error · RuntimeException
Failed to acquire access token
Error message
Failed to acquire access token
What it means
MicrosoftEmailSenderCustomizer obtains an OAuth2 client-credentials access token from Azure AD (MSAL4J ConfidentialClientApplication) so emails can be sent via Microsoft Graph. Any failure in the async acquireToken call — HTTP errors, bad tenant/client id/secret, network issues — is caught and rethrown as this RuntimeException with the original cause attached.
Solutions
- Inspect the wrapped cause (e.getCause()) — it contains MSAL's exact failure (invalid_client, AADSTS error code, IOException)
- Verify cas.email.microsoft.client-id, client-secret and tenant-id are correct and the secret has not expired in Azure Portal (App registrations > Certificates & secrets)
- Ensure the server can reach https://login.microsoftonline.com (proxy/firewall/DNS)
- Grant the app the Mail.Send application permission and admin consent
- Check the authority URL tenant placeholder resolves to a real tenant id
Example fix
// before
cas.email.microsoft.client-secret=${OLD_SECRET}
// after (update Azure portal secret & config)
cas.email.microsoft.client-id=00000000-0000-0000-0000-000000000000
cas.email.microsoft.client-secret=NEW_SECRET_VALUE
cas.email.microsoft.tenant-id=11111111-1111-1111-1111-111111111111 Defensive patterns
Strategy: try-catch
Validate before calling
// preflight: verify required Microsoft mail config is present
if (StringUtils.isBlank(clientId) || StringUtils.isBlank(clientSecret) || StringUtils.isBlank(tenantId)) {
throw new IllegalStateException("Microsoft mail sender requires client-id, client-secret and tenant-id");
} Try / catch
try {
return clientApplication.acquireToken(params).get().accessToken();
} catch (ExecutionException | InterruptedException e) {
logger.error("MSAL token acquisition failed: {}", e.getCause());
throw new MailSenderException("Failed to acquire access token", e);
} Prevention
- Monitor Azure AD app secret expiry and rotate before it lapses
- Grant Mail.Send application permission with admin consent before going live
- Verify outbound connectivity to login.microsoftonline.com from the CAS host
- Always log the MSAL cause, not just the wrapper message
When it happens
Trigger: Calling accessToken() during email-sender customization when msal4j's clientApplication.acquireToken(clientCredentialParameters).get() throws or completes exceptionally: wrong tenantId/clientId/clientSecret, Azure AD unreachable, Conditional Access blocking the app, or an expired/revoked secret.
Common situations: Expired or rotated Azure client secret not updated in CAS config; wrong tenant id; outbound firewall/proxy blocking login.microsoftonline.com; app not granted Mail.Send application permission; tenant misconfigured for the authority URL.
Related errors
- Failed: status with message
- Invalid credentials:
- not in allowed range.
- Unexpected LDAP error
- Invalid client credentials provided for registered service:
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/cdc363af09e5c8b1.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-mail-microsoft/src/main/java/org/apereo/cas/mail/MicrosoftEmailSenderCustomizer.java:49
if (microsoft.isDefined() && mailSender instanceof JavaMailSenderImpl impl) {
val accessToken = fetchAccessToken();
LOGGER.debug("Setting accessToken as the password: [{}]", accessToken);
impl.setPassword(accessToken);
}
}
protected String fetchAccessToken() {
try {
val microsoft = casProperties.getEmailProvider().getMicrosoft();
val clientCredentialParameters = ClientCredentialParameters.builder(microsoft.getScopes()).build();
val clientApplication = ConfidentialClientApplication
.builder(SpringExpressionLanguageValueResolver.getInstance().resolve(microsoft.getClientId()),
ClientCredentialFactory.createFromSecret(SpringExpressionLanguageValueResolver.getInstance().resolve(microsoft.getClientSecret())))
.authority("https://login.microsoftonline.com/%s".formatted(SpringExpressionLanguageValueResolver.getInstance().resolve(microsoft.getTenantId())))
.build();
return clientApplication.acquireToken(clientCredentialParameters).get().accessToken();
} catch (final Exception e) {
throw new RuntimeException("Failed to acquire access token", e);
}
}
}
View on GitHub (pinned to e7288fc434)