Search code examples
c#asp.netoauth-2.0exchangewebservices

Migrating From ExchangeWebService Basic Authentication to OAuth2.0


My goal is to migrate our Exchange connection to use OAuth2.0 so we are covered for the 2020 removal of Basic Authentication.

My Current Code using Basic Authentication is:

ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
service.Credentials = new WebCredentials(MailBox, Password, "domamer");
try
{
    service.AutodiscoverUrl(MailBox, RedirectionUrlValidationCallback);
}
catch
{
    service.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
}

Reviewing the documentation provided by Microsoft Here: (link) I coded the following, with the expectation that it would replace the above.

var pcaOptions = new PublicClientApplicationOptions
{
    ClientId = AppSettings.GetOauthClientID(),
    TenantId = AppSettings.GetOauthTenantID()
};

var pca = PublicClientApplicationBuilder.CreateWithApplicationOptions(pcaOptions).Build();

// The permission scope required for EWS access
var ewsScopes = new string[] { "https://outlook.office.com/EWS.AccessAsUser.All" };

// Make the interactive token request
var authResult = await pca.AcquireTokenInteractive(ewsScopes).ExecuteAsync();

// Configure the ExchangeService with the access token
var ewsClient = new ExchangeService();
ewsClient.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
ewsClient.Credentials = new OAuthCredentials(authResult.AccessToken);
ewsClient.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, MailBox);

I thought "ewsClient" in my new code would be the equivalent of "service" in my original code.

When I try to step through my project, it just ends at this line:

var authResult = await pca.AcquireTokenInteractive(ewsScopes).ExecuteAsync();

I've double checked my ClientID, TenantID are correct.

Has anyone had this problem before? Possible solutions or things to check?

I tried using the Try/Catch in hopes of getting an error message, but I never hit the breakpoints I set on all the Console.WriteLine. It just locks up and stops responding at the ExecuteAsync() line

try
{
    // Make the interactive token request
    var authResult = await pca.AcquireTokenInteractive(ewsScopes).ExecuteAsync();

    // Configure the ExchangeService with the access token
    var ewsClient = new ExchangeService();
    ewsClient.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
    ewsClient.Credentials = new OAuthCredentials(authResult.AccessToken);
    ewsClient.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, EmailBox);

    Console.WriteLine("Made it Here");
}
catch (MsalException ex)
{
    Console.WriteLine($"Error acquiring access token: {ex.ToString()}");
}
catch (Exception ex)
{
    Console.WriteLine($"Error: {ex.ToString()}");
}

Solution

  • After talking with Microsoft on the phone, they struggled to help me get it working as well. It took a while, but I was finally able to make an OAuth connection to Microsoft Services using the following code:

    private const string TenantID = "[Your Tenant ID]";
    private const string ClientID = "[Your Client ID]";
    private const string ClientSecret = "[Your Secret ID]";
    
    private const string AgentName = "My Agent Name";
    
    public static ExchangeService OAuthConnectPost()
    {
        string LoginURL = String.Format("https://login.microsoftonline.com/{0}/oauth2/v2.0/token", TenantID);
    
        var LogValues = new Dictionary<string, string>
        {
            { "grant_type", "client_credentials" },
            { "client_id", ClientID },
            { "client_secret", ClientSecret },
            { "scope", "https://graph.microsoft.com/.default" }
        };
        string postData = "";
        foreach (var v in LogValues)
        {
            postData += (String.IsNullOrWhiteSpace(postData) ? "" : "&") + v.Key + "=" + v.Value;
        }
        var data = Encoding.ASCII.GetBytes(postData);
    
        ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
        ServicePointManager.Expect100Continue = true;
        ServicePointManager.DefaultConnectionLimit = 9999;
        ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls
           | SecurityProtocolType.Tls11
           | SecurityProtocolType.Tls12
           | SecurityProtocolType.Ssl3;
    
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(LoginURL);
        request.Method = "POST";
        request.ContentType = "application/x-www-form-urlencoded";
        request.Accept = "*/*";
        request.UserAgent = AgentName;
        request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore);
        request.ContentLength = data.Length;
        using (var stream = request.GetRequestStream())
        {
            stream.Write(data, 0, data.Length);
        }
    
        using (var response = (HttpWebResponse)request.GetResponse())
        using (Stream stream = response.GetResponseStream())
        using (var reader = new StreamReader(stream))
        {
            var json = reader.ReadToEnd();
            var aToken = JObject.Parse(json)["access_token"].ToString();
    
            var ewsClient = new ExchangeService();
            ewsClient.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
            ewsClient.Credentials = new OAuthCredentials(aToken);
            return ewsClient;
        }
    }
    

    I can then make a connection to the service and authenticate using a user account similar to what I was doing before

    var service = OAuthConnectPost();
    service.Credentials = new WebCredentials(EmailBox, EmailPass, EmailDomain);
    FolderId rootFolderId = new FolderId(WellKnownFolderName.Inbox);
    ItemView view = new ItemView(500);
    FindItemsResults<Item> findResults = service.FindItems(rootFolderId, view);