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

  1. Inspect the wrapped cause (e.getCause()) — it contains MSAL's exact failure (invalid_client, AADSTS error code, IOException)
  2. 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)
  3. Ensure the server can reach https://login.microsoftonline.com (proxy/firewall/DNS)
  4. Grant the app the Mail.Send application permission and admin consent
  5. 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

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


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)