Aspose Email: Issue with EWSClient

We are using EWS client to access microsoft exchange mail box. It was working fine just till last week and now getting unauthorized error(401) when trying to read inbox. Here is code which is doing connect:
EWSClient.useSAAJAPI(true);
System.setProperty(“http.auth.preference”, “NTLM”);
Authenticator.setDefault(getAuthenticator(userName,password, “”));

  IEWSClient client = EWSClient.getEWSClient(new URL(serverUrl));

AND

static Authenticator getAuthenticator(String user, String pw, String domain) {
final String username = domain + “\” + user;
final String password = pw;
[//System.out.println](https://system.out.println/)("New Credentials " + username);
return new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password.toCharArray());
}
};
}

I think, we need OAUTH2 based authorization now because basic authorization is being stopped.
Please help here OAUTH2 because we purchased Aspose Email for email support to client and now its blocker in production.

@amohan123

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.

We tried to use EWS client by setting up Client Credentials Flow in Azure. Few things to know that in this flow scope always ends with .default and scope that works here is Sign in to Outlook.

We are able to do OAUTH2 for IMAP protocol with same setup. Also in same app, full_access_as_app is granted. I can get access token but when EWSClient is being initialized with userName and accessToken provider, we are getting error after some delay: com.aspose.email.system.exceptions.WebException: The request timed out
EWS.AccessAsUser.All is not available in Azure permissions,

Your help is needed as you have helped in past. Its URGENT again. Thanks

@amohan123

We suggest you please read the following article to achieve your requirement. Hope this helps you.
Using OAuth to access Mail Services

Thanks Tahir.
We have implemented EWS logic following that article to access MAPI based mailboxes and IMAP connections are working fine. With EWS, we were using following code to send initialize EWSClient:
// Create instance of ExchangeClient class by giving credentials
NetworkCredential credentials = new NetworkCredential(userName, password);
EWSClient.useSAAJAPI(true);
IEWSClient client = EWSClient.getEWSClient(server, credentials);
It was going but since Oct, we are getting 401 error and then we understood that MAPI no longer supports user/password based basic authentication, so changed it to loke this:
String[] scopes = new String[]{“Sign in to Outlook”}
ClientCredentialGrant tokenProvider = new ClientCredentialGrant(
mailEnvelope.getOutgoingMailTenant(),
mailEnvelope.getOutgoingMailClientId(),
mailEnvelope.getOutgoingMailClientSecret(),
scopes);
NetworkCredential credentials = new OAuthNetworkCredential(userName, tokenProvider);

  IEWSClient client = EWSClient.getEWSClient(server, credentials);

In above code, ClientCredentialGrant copy of
AzureROPCTokenProvider .
Now with new code, we are getting request timed out error, earlier when basic auth was in place, it was giving 401, authorization error. Not sure why, it’s timed out error.

@amohan123

We have logged this problem in our issue tracking system as EMAILJAVA-35115. We will inform you once there is an update available on it.

We apologize for your inconvenience.

Thanks Tahir.

Please update us with this issue, we are eagerly waiting for resolution as deployment is being delayed.

@amohan123

The AzureROPCTokenProvider is an OAuth 2.0 Resource Owner Password Credentials provider, described in more detail on Microsoft website:

Could you please check “EWS.AccessAsUser.All” permission? The “Sign in to Outlook” scope should be used:
API Permissions.png (65.3 KB)

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);

Also, we have prepared 22.9.2 release, to check Oauth2 Impersonation issue:

You need to disable OAUTH Impersonation in the test:

EWSClient.useOAUTHImpersonation(false);

Moreover, you can also use MSAL Java lib to acquire Token:

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

Sample from Microsoft website:

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;
}

Thanks Tahir,
Your details are really great. As I mentioned earlier, I am able to get access token and that class is implemented based on MASL jar and its client credential flow setup on Azure. ROPC flow is not setup and it’s not advised too. In client credential flow, only one scope is accepted which is
https://outlook.office365.com/.default

We have also IMAP accounts and access token is working for IMAP. This same azure app is set up with full_access_as_app

Problem is when I am using username and password, we are getting authentication failed error but when OAuthNetworkCredential is initialized with username and obtained access token, we are getting request timed out error after some delay. I hope, I am able to explain issue.

@amohan123

We have logged your concerns in our issue tracking system. We will inform you once there is any update available on it.

@amohan123

Please make sure that you are using the new release shared in this thread with impersonation disabled.

Thanks Tahir, We tried with 22.9.2 release with EWSClient.useOAUTHImpersonation(false) but no luck. Still we are getting request time out error and its appear after some delay. No sure what to change, we bought Aspose email product only to support MAPI and we are stuck.

Some logs:

java.lang.RuntimeException: class com.aspose.email.system.exceptions.WebException: The request timed out
com.aspose.email.internal.o.zab.x(Unknown Source)
com.aspose.email.zaiw.a(SourceFile:98)
com.aspose.email.zsz.a(SourceFile:34)
com.aspose.email.zh.a(SourceFile:294)
com.aspose.email.zh.a(SourceFile:441)
com.aspose.email.EWSClient.a(SourceFile:450)
com.aspose.email.EWSClient.getEWSClient(SourceFile:396)
com.aspose.email.EWSClient.getEWSClient(SourceFile:381)
com.aspose.email.EWSClient.getEWSClient(SourceFile:148)

: com.aspose.email.system.exceptions.WebException: The request timed out
at com.aspose.email.internal.o.zab.x(Unknown Source) ~[aspose-email-22.9.2-jdk16.jar:22.9.2]
at com.aspose.email.zaiw.a(SourceFile:98) ~[aspose-email-22.9.2-jdk16.jar:22.9.2]
at com.aspose.email.zsz.a(SourceFile:34) ~[aspose-email-22.9.2-jdk16.jar:22.9.2]
at com.aspose.email.zh.a(SourceFile:294) ~[aspose-email-22.9.2-jdk16.jar:22.9.2]
… 20 common frames omitted

@amohan123

We have logged this detail in our issue tracking system. We will inform you once there is an update available on it.

@amohan123

We have closed this ticket EMAILJAVA-35115.

You are using EWSClient.useSAAJAPI(true) option. But the error you are getting is (WebException ) from old exchange implementation.

We cannot get that kind of exception with EWSClient.useSAAJAPI(true) option.

The EWSClient.useSAAJAPI(true) uses the new exchange client implementation and we strongly recommend using the SAAJAPI implementation. We need to be sure, that the option EWSClient.useSAAJAPI(true) enabled in the your test cases.

Thank you Tahir. Using EWSClient.useSAAJAPI(true) solved issue. Now its working with OAUTH2.