OAuth2 support for O365

Microsoft has declared end of life of OAuth as of October 2020, so will the Aspose.Email library be enhanced to support OAuth2 for O365?

@bmalaga,

I like to inform that we are already use OAuth2 instead of OAuth.

Thanks Adnan! We just found that OAuth2 is supported deep in the OAuth token class documentation. As a follow-up question -
Today we use the EWSClient to connect to Office365, with Microsoft deprecating EWS and no longer supporting OAuth and only OAuth2, will we still be able to use the Aspose.Email.Clients.Exchange.WebService.EWSClient to connect with Office365 accounts past October 2020?

@bmalaga,

Yes, you will be able to use with OAuth2.

1 Like

Hi,
I was just wondering whether this sample code from your online doco is using OAuth2 or not.

private static IEWSClient GetExchangeEWSClient()
{
const string mailboxUri = “[https://outlook.office365.com/ews/exchange.asmx ](https://outlook.office365.com/ews/exchange.asmx)”;
const string domain = @"";
const string username = @“username@ASE305.onmicrosoft.com”;
const string password = @“password”;
NetworkCredential credentials = new NetworkCredential(username, password, domain);
IEWSClient client = EWSClient.GetEWSClient(mailboxUri, credentials);
return client;
}

If not, is it just Basic Authentication? Therefore does it need to be adjusted in order to ensure not affected by Microsoft’s plan to turn of Basic Authentication for Online exchange in Oct 2020?

Thanks, Julie

@t1jsw,

The EWS client supports OAuth authentication. Please check the below code sample to see how to use this feature.

ITokenProvider tokenProvider = new AzureROPCTokenProvider("Tenant", "ClientId", "EMail", "Password");
NetworkCredential credentials = new OAuthNetworkCredential(tokenProvider);
IEWSClient client = EWSClient.GetEWSClient("EWSUrl", credentials);

OAuth supports different ways for retrieving access token, and Microsoft may change them.
That’s why you have has to implement simple interface of a token provider by your self.

The following is code sample of simple implementation of ITokenProvider. It implements resource owner password credential (ROPC) grant mechanism, i.e. provides access token by login and password.

// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc
internal class AzureROPCTokenProvider : ITokenProvider
{
    private const string uriFormat = "https://login.microsoftonline.com/{0}/oauth2/v2.0/token";
    private const string bodyFormat =
        "client_id={0}" +
        "&scope={1}" +
        "&username={2}" +
        "&password={3}" +
        "&grant_type={4}";
    private readonly static string[] scopeAr = new string[]
    {
        // O365 ----------------------
        // Exchange Web Services will not receive feature updates
        // Basic Authentication for EWS will be decommissioned
        // https://developer.microsoft.com/en-us/graph/blogs/upcoming-changes-to-exchange-web-services-ews-api-for-office-365/

        "https://outlook.office.com/EWS.AccessAsUser.All",

        // ---------------------------

        // Graph ---------------------

        //"User.Read",
        //"User.ReadWrite",
        //"Mail.Read",
        //"Mail.ReadWrite",
        //"Mail.Send",

        // ---------------------------
    };
    private readonly static string scope = string.Join(" ", scopeAr);
    private const string grant_type = "password";
    private readonly object tokenSyncObj = new object();
    private OAuthToken token;
    private readonly string tenant;
    private readonly string clientId;
    private readonly string userName;
    private readonly string password;

    /// <summary>
    /// Initializes a new instance of the <see cref="AzureROPCTokenProvider"/> class
    /// </summary>
    /// <param name="tenant"></param>
    /// <param name="clientId"></param>
    /// <param name="scope"></param>
    /// <param name="userName"></param>
    /// <param name="password"></param>
    public AzureROPCTokenProvider(string tenant, string clientId, string userName, string password)
    {
        this.tenant = tenant;
        this.clientId = clientId;
        this.userName = userName;
        this.password = password;
    }

    /// <summary>
    /// Gets oAuth access token. 
    /// </summary>
    /// <param name="ignoreExistingToken">
    /// If ignoreExistingToken is true, requests new token from a server. Otherwise behaviour is depended on whether token exists or not.
    /// If token exists and its expiration date is not expired returns current token, otherwise requests new token from a server.
    /// </param>
    /// <returns>Returns oAuth access token</returns>
    public virtual OAuthToken GetAccessToken(bool ignoreExistingToken)
    {
        lock (tokenSyncObj)
        {
            if (this.token != null && !this.token.Expired && !ignoreExistingToken)
                return this.token;
            token = null;
            string uri = string.Format(uriFormat, tenant);
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
            string body = string.Format(bodyFormat,
                HttpUtility.UrlEncode(clientId),
                HttpUtility.UrlEncode(scope),
                HttpUtility.UrlEncode(userName),
                HttpUtility.UrlEncode(password),
                HttpUtility.UrlEncode(grant_type));
            byte[] bytes = Encoding.ASCII.GetBytes(body);
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            request.ContentLength = bytes.Length;
            MemoryStream ms = new MemoryStream(bytes);
            using (Stream requestStream = request.GetRequestStream())
                requestStream.Write(bytes, 0, bytes.Length);
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            StringBuilder responseText = new StringBuilder();
            bytes = new byte[1024];
            int read = 0;
            using (Stream stream = response.GetResponseStream())
            {
                while ((read = stream.Read(bytes, 0, bytes.Length)) > 0)
                    responseText.Append(Encoding.ASCII.GetString(bytes, 0, read));
            }
            string jsonString = responseText.ToString();
            AzureTokenResponse t = JsonConvert.DeserializeObject<AzureTokenResponse>(jsonString);
            token = new OAuthToken(
                t.access_token,
                TokenType.AccessToken,
                DateTime.Now.AddSeconds(t.expires_in));
            return token;
        }
    }

    /// <summary>
    /// Gets oAuth access token.
    /// If token exists and its expiration date is not expired returns current token, otherwise requests new token from a server.
    /// </summary>
    /// <returns>Returns oAuth access token</returns>
    public OAuthToken GetAccessToken()
    {
        return GetAccessToken(false);
    }

    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    public virtual void Dispose()
    {
    }
}
1 Like

Hi,
I try to use your AzureROPCTokenProvider but I get an exception:

The remote server returned an error: (400) Bad request

while trying getResponse from server

in your code. I’ve changed an uriFormat from

private const string uriFormat = @“Sign in to your account”;

to

private const string uriFormat = @“Sign in to your account”;

but the host returned an authorization HTML form to enter a credential information…
Could you help me to understand how I should authorize by EWSClient using OAuth 2.0 without entering credential in a stand-alone popup windows just programmaticaly using code.

Thank you

@cap.aspose,

We have investigated this on our end and this is not a problem at all. The provided examples are all correct. Simply, the ROPC provider needs to used for appropriate type of account. The problem is that the you are probably using a personal one. You can also work with it, but you must implement its ITokenProvider using the correct method for getting the token. If you need to further investigate, please check below link.

Hello
Thank you.
I’ve implemented my ITokenProvider and I’ve successfully got an AccessToken, but when I tried using EWSClient

using (IEWSClient client = EWSClient.GetEWSClient(mailboxUri, credentials))
{…}

with this credentials I got an exception:

{System.Net.WebException: The request failed with HTTP status 401: Unauthorized.
at System.Web.Services.Protocols.SoapHttpClientProtocol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall)
at System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(String methodName, Object[] parameters)
at #=zL93QXDl45q8$DylQcAFBaV36B1W8acxiZKJ9WtQFT2J$dyJBGg==.#=zv01V90c=(String #=zyrrtf80=, Object[] #=zAasB8vM=)
at #=zL93QXDl45q8$DylQcAFBaV36B1W8acxiZKJ9WtQFT2J$dyJBGg==.GetFolder(GetFolderType GetFolder1)
at Aspose.Email.Clients.Exchange.WebService.EWSClient.GetEWSClient(String mailboxUri, ICredentials credentials, WebProxy proxy)
at Aspose.Email.Clients.Exchange.WebService.EWSClient.GetEWSClient(String mailboxUri, ICredentials credentials)

Could you clarify when I was wrong?
Thank you

@cap.aspose,

I have observed the issue shared and like to share that we are verifying on our end and will share the possible outcome with you as soon as the requirement will be addressed.

@mudassir.fayyaz I have also tried to implement this and receive the same error as @cap.aspose.

I look forward to further configuration details.

Thanks, Julie

@t1jsw,

We will share the good news with you as soon as we have a solution.

Hello, any update on this one?

@cap.aspose,

I have verified from our team and regret to share that we are still working over it and request for your patience. We will share updates with you as soon as possible in this thread.

Hi,

Sorry to be a pain but I was just wondering if there was any progress with this? I need to get alternative solutions out to customers before Basic Auth is disabled for online exchange in October.

Thanks, Julie

We have investigated the requirements on our end. Instead of using:

string[] scopeAr = new string[]
                        {  "https://outlook.office.com/EWS.AccessAsUser.All",

                        };

you have to use :

string[] scopeAr = new string[]
            {  
                "User.ReadWrite",
                "Mail.ReadWrite",
                "Mail.Send",
                "Notes.Create",
                "Notes.ReadWrite",
                "Notes.ReadWrite.All",
                "MailboxSettings.ReadWrite",
                "Contacts.ReadWrite",
                "Contacts.ReadWrite.Shared",
            };

Please also check this link . You will also have to setup this permissions in azure application. Please read this article for details. If you need account in Azure please use this program .

Hi @mudassir.fayyaz,

Thank you for this. I have managed to get back a token and connect to client. I am also able to return MailboxInfo however I get an error when trying to load messages from the Inbox.

My code looks like this:
Dim loMsgs As Aspose.Email.Clients.Exchange.ExchangeMessageInfoCollection = loClient.ListMessages(loMailboxInfo.InboxUri)

Error that occurs is:
Error: Application Error Occurred:
Error Number: NullReferenceException
Error Source: Aspose.Email
at #=zf7zry2QqR2YGZXbixY9avYSW8TyGePjAOQUTwfdYMDKQ.#=znPoKAC8$8isp(ItemType #=zqrNQD10=)
at #=zf7zry2QqR2YGZXbixY9avYSW8TyGePjAOQUTwfdYMDKQ.#=zu6gEszDlfb9c(IList1 #=z64vHbP4=, IEnumerable1 #=ztOyAWL8=)
at #=zf7zry2QqR2YGZXbixY9avYSW8TyGePjAOQUTwfdYMDKQ.#=zu6gEszDlfb9c(String #=zy3cUgPI=, Int32 #=zEvVZXQmd5nfy, MailQuery #=zwjN1twU=, ExchangeListMessagesOptions #=zxEbtKhM=, IEnumerable`1 #=ztOyAWL8=)
at #=zf7zry2QqR2YGZXbixY9avYSW8TyGePjAOQUTwfdYMDKQ.ListMessages(String #=zy3cUgPI=, ExchangeListMessagesOptions #=zxEbtKhM=)
at #=zf7zry2QqR2YGZXbixY9avYSW8TyGePjAOQUTwfdYMDKQ.ListMessages(String #=zy3cUgPI=)
at XXXXXXX
Error Description: Object reference not set to an instance of an object.

I am not sure what I am doing wrong. The mailbox is small with only 1 email in it.

Thanks, Julie

@t1jsw,

I have observed the issue shared by you and like to share that using latest version 20.2 and implying oAuth approach we have not been able to reproduce the issue. If the issue is still reproducing using latest version then it would be better, if you may please share the working sample project along with test account credentials to verify the same on our end. This issue is different than oAuth issue.

Hi,

I can confirm upgrading from 20.1 to 20.2 has resolved this issue.
Thank you for all your help with this.

Cheers, Julie

@t1jsw,

It’s good to know things are finally resolved and working on your end.