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()}");
}
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);