Free Support Forum - aspose.com

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”;
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 = @“https://login.microsoftonline.com/{0}/oauth2/v2.0/token”;

to

private const string uriFormat = @“https://login.microsoftonline.com/{0}/oauth2/authorize”;

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.