EWS Modern Authentication in Java

Is there an example on how to do “Modern Authentication” (Oauth) using the EWSClient in Java?

I implemented the solution at OAuth2 support for O365 - #8 by cap.aspose but I receive a timeout.

@mizehrer

You can use AzureROPCTokenProvider class described in the MS Graph.

Code sample to initialize credentials:

ITokenProvider provider = new AzureROPCTokenProvider(oauth2.Tenant, oauth2.ClientId, "", oauth2.userNameEmail, oauth2.userPassword, 
    new String[] { "https://outlook.office.com/EWS.AccessAsUser.All" });
NetworkCredential credentials = new OAuthNetworkCredential(oauth2.userNameEmail, provider);

Setting the Azure “Office 365 Exchange Online”/full_access_as_app permission:

  • Application/Manage/API permissions/ + Add a permission

  • Select “APIs my organization uses” tab and search for “Office 365 Exchange Online” as shown in this image : image1.png (24.1 KB)

  • Select “Application permissions” and enable “full_access_as_app” as shown in this image : image2.png (46.7 KB)

MS Documentation link:

Access Mail Services using OAuth

Hope this helps you.

Hi, thank you for the hints. This is what I already implemented. If I use the example for the AzureROPCTokenProvider as is, I receive the following error:

java.lang.IllegalAccessError: Operation failed: 401/Unauthorized
Details:
{2}{"error":"invalid_client","error_description":"AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'.\r\nTrace ID: 0c8b5597-e1f6-49f0-811a-7bc007c40a00\r\nCorrelation ID: 3af60d7d-1837-4f14-b149-7f1f1434dcfb\r\nTimestamp: 2022-08-03 10:40:06Z","error_codes":[7000218],"timestamp":"2022-08-03 10:40:06Z","trace_id":"0c8b5597-e1f6-49f0-811a-7bc007c40a00","correlation_id":"3af60d7d-1837-4f14-b149-7f1f1434dcfb","error_uri":"https://login.microsoftonline.com/error?code=7000218"}

If I add the client secret to the body parameters, I end up in a timeout with no useful information about the cause.

But this method (with adding the client_secret in the AzureROPCTokenProvider) works for IMAP using the Sign in to Outlook scope.

@mizehrer

We have logged this problem in our issue tracking system as EMAILJAVA-35085. You will be notified via this forum thread once this issue is resolved.

We apologize for your inconvenience.

@mizehrer

We suggest you to use MSAL Java lib to acquire Token:

https://mvnrepository.com/artifact/com.microsoft.azure/msal4j/1.13.2

Please check the following sample code from Microsoft site:

private static IAuthenticationResult acquireTokenUsernamePassword(PublicClientApplication pca,
        Set<String> scope,
        IAccount account,
        String username,
        String password) throws Exception {
    IAuthenticationResult result;
    try {
        SilentParameters silentParameters =
                SilentParameters
                        .builder(scope)
                        .account(account)
                        .build();
        // Try to acquire token silently. This will fail on the first acquireTokenUsernamePassword() call
        // because the token cache does not have any data for the user you are trying to acquire a token for
        result = pca.acquireTokenSilently(silentParameters).join();
        System.out.println("==acquireTokenSilently call succeeded");
    } catch (Exception ex) {
        if (ex.getCause() instanceof MsalException) {
            System.out.println("==acquireTokenSilently call failed: " + ex.getCause());
            UserNamePasswordParameters parameters =
                    UserNamePasswordParameters
                            .builder(scope, username, password.toCharArray())
                            .build();
            // Try to acquire a token via username/password. If successful, you should see
            // the token and account information printed out to console
            result = pca.acquireToken(parameters).join();
            System.out.println("==username/password flow succeeded");
        } else {
            // Handle other exceptions accordingly
            throw ex;
        }
    }
    return result;
}

public static String getAccessToken() throws Exception {
    Set<String> scope = new HashSet<String>();
    scope.add("https://outlook.office365.com/.default");
    String username = "test1@onmicrosoft.com";
    String password = "userPass";
    String clientId = "xxxxxb-f4be-4e2e-95dd-7aa4f5dxxxxx";
    String tenantId = "xxxxx65f-f7e3-4bc3-841f-13b29xxxxx";
    String authority =
            "https://login.microsoftonline.com/" + tenantId + "/oauth2/v2.0/token";
    PublicClientApplication pca = PublicClientApplication.builder(clientId)
            .authority(authority)
            .build();

    //Get list of accounts from the application's token cache, and search them for the configured username
    //getAccounts() will be empty on this first call, as accounts are added to the cache when acquiring a token
    Set<IAccount> accountsInCache = pca.getAccounts().join();
    IAccount account = getAccountByUsername(accountsInCache, username);

    //Attempt to acquire token when user's account is not in the application's token cache
    IAuthenticationResult result = acquireTokenUsernamePassword(pca, scope, account, username, password);
    System.out.println("Account username: " + result.account().username());
    System.out.println("Access token:     " + result.accessToken());
    System.out.println("Id token:         " + result.idToken());
    System.out.println();

    accountsInCache = pca.getAccounts().join();
    account = getAccountByUsername(accountsInCache, username);

    //Attempt to acquire token again, now that the user's account and a token are in the application's token cache
    result = acquireTokenUsernamePassword(pca, scope, account, username, password);
    System.out.println("Account username: " + result.account().username());
    System.out.println("Access token:     " + result.accessToken());
    System.out.println("Id token:         " + result.idToken());

    return "";
}

private static IAccount getAccountByUsername(final Set<IAccount> accountsInCache, final String username) {
    if (accountsInCache.isEmpty()) {
        System.out.println("==No accounts in cache");
    } else {
        System.out.println("==Accounts in cache: " + accountsInCache.size());
        for (IAccount account : accountsInCache) {
            if (account.username().equals(username)) {
                return account;
            }
        }
    }
    return null;
}