Warm tip: This article is reproduced from stackoverflow.com, please click
asp.net c# oauth-2.0 exchangewebservices

Migrating From ExchangeWebService Basic Authentication to OAuth2.0

发布于 2020-04-07 10:09:29

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()}");
}
Questioner
MrMatt
Viewed
159
MrMatt 2020-01-31 20:28

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