Read Mails from Outlook Web App using .net - c#

I have code that works for office 365 Exchange web service https://outlook.office365.com/EWS/Exchange.asmx. However I am now trying to read mail from outlook web app say https://mail.company.com/owa
ExchangeService exchangeService = new ExchangeService(ExchangeVersion.Exchange2013);
exchangeService.Credentials = new NetworkCredential(i_EmailID, i_Password);
exchangeService.Url = new Uri(exchangeURL);
exchangeService.KeepAlive = true;
Mailbox mailbox = new Mailbox(email);
FolderId folder = new FolderId(WellKnownFolderName.Inbox, mailbox);
ItemView view = new ItemView(1);
FindItemsResults<Item> items = exchangeService.FindItems(folder, view);
If this code works only for Exchange Web Service then what method should I be using to access Outlook Web Access?
Please share me any article or code sample that can help me understand in getting this implemented.
UPDATE: Exchange Online deprecating Basic Authentication (Basic Auth)
Basic Authentication is no longer supported by Exchange Online. Use the
OAuth 2.0 authorization code flow
https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-authenticate-an-ews-application-by-using-oauth
var confidentialClientApplicationBuilder = ConfidentialClientApplicationBuilder.Create(appId)
.WithClientSecret(clientSecret)
.WithTenantId(tenantId)
.Build();
string[] ewsScopes = new string[] { "https://outlook.office365.com/.default" };
var authResult = await confidentialClientApplicationBuilder.AcquireTokenForClient(ewsScopes)
.ExecuteAsync();
return authResult.AccessToken;
and then use the token to pass to OAuthCredentials
exchangeService.Credentials = new OAuthCredentials(accessToken);

Related

Getting 403 error when trying to get Folder items from Office 365 mailbox using ExchangeService

I'm trying to read all Inbox email items from an Office 365 mailbox using ExchangeService.
For that, I:
Created an app in my AzureAD portal.
Given this app all permissions.
Issues this app an access secret to use in my code.
The code works to the point that I sucessfully get a token, but when trying to get the folder items I get an 403 error:
'The request failed. The remote server returned an error: (403)
Forbidden.'
I get this error from my dev and my prod environments so I'm pretty sure it's not a network or port issue.
Here's my code:
var cca = ConfidentialClientApplicationBuilder
.Create("myApplicationId")
.WithClientSecret("myClientSecret")
.WithTenantId("myTenantId")
.Build();
var ewsScopes = new string[] { "https://outlook.office365.com/.default" };
// This is where I get the token
var authResult = await cca.AcquireTokenForClient(ewsScopes).ExecuteAsync();
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, "my#mail.box");
ewsClient.HttpHeaders.Add("X-AnchorMailbox", "my#mail.box");
// This is where I get the 403 error:
var items = ewsClient.FindItems(
new FolderId(WellKnownFolderName.Inbox, new Mailbox("my#mail.box")),
new SearchFilter.SearchFilterCollection(LogicalOperator.And, new SearchFilter[] {}
),
new ItemView(15)
);
403 if its coming back from Office365 sounds like they have either disabled EWS on the Mailbox your trying to access or they have limited the clients that are allowed to connect eg https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/how-to-control-access-to-ews-in-exchange . You could try testing EWS itself using a user account via the EWSeditor https://github.com/dseph/EwsEditor

EWS Oauth Exception: The request failed. The remote server returned an error: (401) Unauthorized

I'm trying to create a bot that can book meetings. In order to do that i need to access the calendar of an employee to get FreeBusy info to ultimately book a meeting. I'm trying to avoid hardcoding the email and password and for that I want to use an access token from Azure AD to call EWS.
I set the properties for
public static ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013);
using this method:
public static async System.Threading.Tasks.Task UseExchangeService(IDialogContext context, string userEmailAddress, SecureString userPassword)
{
string authority = ConfigurationManager.AppSettings["authority"];
string clientID = ConfigurationManager.AppSettings["clientID"];
string resource = ConfigurationManager.AppSettings["resource"];
string appKey = ConfigurationManager.AppSettings["appkey"];
AuthenticationContext authenticationContext = new AuthenticationContext(authority, false);
ClientCredential clientCred = new ClientCredential(clientID, appKey);
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(resource, clientCred);
service.Url = new Uri(ConfigurationManager.AppSettings["serverName"] + "/ews/exchange.asmx");
service.TraceEnabled = true;
service.TraceFlags = TraceFlags.All;
service.Credentials = new OAuthCredentials(authenticationResult.AccessToken);
// USING THIS LINE IT WORKS FINE!
// service.Credentials = new NetworkCredential(userEmailAddress, userPassword); // VIRKER
}
I do get the access token from Azure AD and I have granted the permission for the application in Azure AD.
I use this method to extract the freebusytimes, it contains other more code to display the times as buttons on a herocard, but this is the call to EWS:
List<AttendeeInfo> attendees = new List<AttendeeInfo>();
attendees.Add(new AttendeeInfo()
{
SmtpAddress = "MyEMAIL",
AttendeeType = MeetingAttendeeType.Organizer
});
attendees.Add(new AttendeeInfo()
{
SmtpAddress = BookersEmail,
AttendeeType = MeetingAttendeeType.Required
});
//DateTime date1 = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day.Ad, 7, 0, 0)
// Specify options to request free/busy information and suggested meeting times.
AvailabilityOptions availabilityOptions = new AvailabilityOptions();
availabilityOptions.GoodSuggestionThreshold = 49;
availabilityOptions.MaximumNonWorkHoursSuggestionsPerDay = 0;
availabilityOptions.MaximumSuggestionsPerDay = 20;
// Note that 60 minutes is the default value for MeetingDuration, but setting it explicitly for demonstration purposes.
availabilityOptions.MeetingDuration = 60;
availabilityOptions.MinimumSuggestionQuality = SuggestionQuality.Excellent;
//TimeWindow hej = new TimeWindow();
DateTime StartDay = DateTime.Now.AddDays(1);
TimeSpan ts = new TimeSpan(9, 0, 0);
DateTime StartTime = StartDay.Date + ts;
availabilityOptions.DetailedSuggestionsWindow = new TimeWindow(StartTime, DateTime.Now.AddDays(4));
availabilityOptions.RequestedFreeBusyView = FreeBusyViewType.FreeBusy;
// Return free/busy information and a set of suggested meeting times.
// This method results in a GetUserAvailabilityRequest call to EWS.
GetUserAvailabilityResults results = service.GetUserAvailability(attendees,
availabilityOptions.DetailedSuggestionsWindow,
AvailabilityData.FreeBusyAndSuggestions,
availabilityOptions);
I have created the application in Azure AD and I have granted the following permissions:
Office 365 Exchange Online:
Use Exchange Web Services with full access to all mailboxes
Read and write calendars in all mailboxes
Read and write user and shared calendars
Access mailboxes as the signed-in user via Exchange Web Services
I've tried other answers i found on stackoverflow, however they do not do the trick for me.
Hope You can help
I am not familiar with EWS, however as far as I know that the Microsoft Graph also provide the similar feature for find the available meeting time using the rest below( refer here):
POST /me/findMeetingTimes
And if you want to using this REST for the web application so that your web app can delegate the sign-in user to perform the operation for Exchange, we can use the OAuth 2.0 code grant flow. And for how to use this flow to integrate with Microsoft Graph, you can refer the links below:
Get started with Microsoft Graph in an ASP.NET 4.6 MVC app
And here is the detail for this flow:
Authorize access to web applications using OAuth 2.0 and Azure Active Directory
Hope it is helpful.

How to find the SharePoint Path from Office 365 using OAuth2 Client Credential Flow

Problem
I have been trying to figure out how to find a SharePoint path for a user when using OAuth2 Client Credential Flow (where an application has permission to read all users' SharePoint files using an Office 365 administrator's one-time acceptance)
I have my client application setup in Azure and am able to read files if I hard-code the SharePoint URL - so I know it is setup correctly.
But I need to "discover" the SharePoint URL so it will be change-tolerant and reusable across customers.
Related Articles:
Different OAuth2 Flows
Using OAuth2 Flow for Exchange
Code
var azureAdAuthority = "https://login.windows.net/{tenant-id}/oauth2/authorize".Replace("{tenant-id}", tenantId);
var discoveryUri = "https://api.office.com/discovery/v1.0/me/";
var discoveryResourceUri = "https://api.office.com/discovery/";
// discover contact endpoint
var cert = new X509Certificate2(certFilePath, certFilePassword, X509KeyStorageFlags.MachineKeySet);
var clientAssertion = new ClientAssertionCertificate(clientId, cert);
var userIdentifier = new UserIdentifier(userObjectId, UserIdentifierType.UniqueId);
var userAssertion = new UserAssertion(userObjectId);
// create auth context
var authContext = new AuthenticationContext(azureAdAuthority, false);
// create O365 discovery client
var discovery = new DiscoveryClient(new Uri(discoveryUri),
() => authContext.AcquireTokenSilent(discoveryResourceUri, clientAssertion, userIdentifier).AccessToken);
// query discovery service for endpoint for 'calendar' endpoint
var dcr = await discovery.DiscoverCapabilityAsync("MyFiles");
This and many other variations throw exceptions from the AcquireTokenSilent function.
If I don't use a "userIdentifier" and call the AcquireToken function it succeeds, but the DiscoverCapabilityAsync function fails.

SMTP and OAuth 2

Does .NET support SMTP authentication via OAuth protocol? Basically, I would like to be able to send emails on users' behalves using OAuth access tokens. However, I couldn't find a support for this in the .NET framework.
Google provides some samples for this in other environments but not .NET.
System.Net.Mail does not support OAuth or OAuth2. However, you can use MailKit's (note: only supports OAuth2) SmtpClient to send messages as long as you have the user's OAuth access token (MailKit does not have code that will fetch the OAuth token, but it can use it if you have it).
The first thing you need to do is follow Google's instructions for obtaining OAuth 2.0 credentials for your application.
Once you've done that, the easiest way to obtain an access token is to use Google's Google.Apis.Auth library:
var certificate = new X509Certificate2 (#"C:\path\to\certificate.p12", "password", X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential (new ServiceAccountCredential
.Initializer ("your-developer-id#developer.gserviceaccount.com") {
// Note: other scopes can be found here: https://developers.google.com/gmail/api/auth/scopes
Scopes = new[] { "https://mail.google.com/" },
User = "username#gmail.com"
}.FromCertificate (certificate));
bool result = await credential.RequestAccessTokenAsync (CancellationToken.None);
// Note: result will be true if the access token was received successfully
Now that you have an access token (credential.Token.AccessToken), you can use it with MailKit as if it were the password:
using (var client = new SmtpClient ()) {
client.Connect ("smtp.gmail.com", 587, SecureSocketOptions.StartTls);
// use the access token
var oauth2 = new SaslMechanismOAuth2 ("username#gmail.com", credential.Token.AccessToken);
client.Authenticate (oauth2);
client.Send (message);
client.Disconnect (true);
}
I got it working by using Microsoft.Identity.Client and MailKit.Net.Smtp.SmtpClient like this using Office 365 / Exchange Online. App registration requires API permissions SMTP.Send.
var options = new PublicClientApplicationOptions
{
ClientId = "00000000-0000-0000-0000-000000000000",
TenantId = " 00000000-0000-0000-0000-000000000000",
RedirectUri = "http://localhost"
};
var publicClientApplication = PublicClientApplicationBuilder
.CreateWithApplicationOptions(options)
.Build();
var scopes = new string[] {
"email",
"offline_access",
"https://outlook.office.com/SMTP.Send" // Only needed for SMTP
};
var authToken = await publicClientApplication.AcquireTokenInteractive(scopes).ExecuteAsync();
//Test refresh token
var newAuthToken = await publicClientApplication.AcquireTokenSilent(scopes, authToken.Account).ExecuteAsync(cancellationToken);
var oauth2 = new SaslMechanismOAuth2(authToken.Account.Username, authToken.AccessToken);
using (var client = new SmtpClient())
{
await client.ConnectAsync("smtp.office365.com", 587, SecureSocketOptions.StartTls);
await client.AuthenticateAsync(oauth2);
var message = new MimeMessage();
message.From.Add(MailboxAddress.Parse(authToken.Account.Username));
message.To.Add(MailboxAddress.Parse("toEmail"));
message.Subject = "Test";
message.Body = new TextPart("plain") { Text = #"Oscar Testar" };
await client.SendAsync(message, cancellationToken);
await client.DisconnectAsync(true);
}
Based on this example:
https://github.com/jstedfast/MailKit/blob/master/ExchangeOAuth2.md
Just adding to the above answer. I also spend lot of time to find out things for sending email using gmail oAuth2 with mailkit in .net. As I am using this to send email to my App users. Thanks to mailkit developers.
Now we need:
Authorization code
Client ID
Client Secret
Refresh Token
Access Token
You can directly get the Client Id and Client Secret from google console by creating your project.
Next you can enable gmail app from the Google Developers OAuth Playground by using your own OAuth credentials in left top setting button.
After that Select and Authorize the API https://mail.google.com/.
Now you can directly refresh token by this http POST request https://developers.google.com/oauthplayground/refreshAccessToken. you will find the parameter in there.
Now you can directly use this code in your C# code using MailKit:
using (var client = new SmtpClient())
{
client.Connect("smtp.gmail.com", 587, SecureSocketOptions.StartTls);
var oauth2 = new SaslMechanismOAuth2(GMailAccount, token.AccessToken);
client.Authenticate(oauth2);
await client.SendAsync(mailMessage);
client.Disconnect(true);
}
Now you will be able to send email through your gmail account from server side.
Using MailKit as referenced in the other answers, I was hitting an authentication issue requiring more scopes to be requested from Gmail. For anyone experiencing "Authentication Failed error" with either of the other answers, this answer uses the Gmail API instead in order to avoid requesting more scopes.
Using some pieces from this answer: https://stackoverflow.com/a/35795756/7242722
Here's a complete example which worked for me:
var fromAddress = new MailboxAddress(fromName, fromEmail);
var toAddress = new MailboxAddress(toName, toEmail);
List<MailboxAddress> ccMailAddresses = new List<MailboxAddress>();
if (ccEmails != null)
foreach (string ccEmail in ccEmails)
ccMailAddresses.Add(new MailboxAddress(string.Empty, ccEmail));
var message = new MimeMessage();
message.To.Add(toAddress);
message.From.Add(fromAddress);
message.Subject = subject;
var bodyBuilder = new BodyBuilder();
bodyBuilder.HtmlBody = body;
bodyBuilder.TextBody = HtmlUtilities.ConvertToPlainText(body);
message.Body = bodyBuilder.ToMessageBody();
foreach (MailboxAddress ccMailAddress in ccMailAddresses)
message.Cc.Add(ccMailAddress);
GoogleAuthorizationCodeFlow authorizationCodeFlow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets()
{
ClientId = <ClientId>,
ClientSecret = <ClientSecret>
},
});
TokenResponse tokenResponse = await authorizationCodeFlow.RefreshTokenAsync("id", <RefreshToken>, CancellationToken.None);
UserCredential credential = new UserCredential(authorizationCodeFlow, "id", tokenResponse);
var gmailService = new GmailService(new BaseClientService.Initializer()
{
ApplicationName = <AppName>,
HttpClientInitializer = credential,
});
Google.Apis.Gmail.v1.Data.Message gmailMessage = new Google.Apis.Gmail.v1.Data.Message();
gmailMessage.Raw = Utilities.Base64UrlEncode(message.ToString());
var result = gmailService.Users.Messages.Send(gmailMessage, "me").Execute();

Exchange Web Services Managed API: Accessing other users items

Is it possibly to access the folders and items of other Exchange accounts other than the one of the logged in user?
Can I do this via Exchange Web Services Managed API?
Yes it is possible, but you should know the password of the other user or grab in some ways this credentials (NetworkCredential object). The typical first lines of you code could be
ExchangeService myService = new ExchangeService (ExchangeVersion.Exchange2007_SP1);
myService.Credentials = new NetworkCredential ("user#mycorp.local", "P#ssword00");
so you can access Exchange Server Web Services with the account which is other as the current user. See ExchangeService object description for more information.
If you are an admin you can make user impersonation by SMTP address.
Here's how you do it without impersonation or knowing credentials.
ExchangeService _service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);
//CREDENTIALS OF AN ACCOUNT WHICH HAS READ ACCESS TO THE CALENDAR YOU NEED
_service.Credentials = new WebCredentials(username, password);
_service.Url = new Uri(serviceURL);
SearchFilter.SearchFilterCollection searchFilter = new SearchFilter.SearchFilterCollection();
searchFilter.Add(new SearchFilter.IsGreaterThanOrEqualTo(AppointmentSchema.Start, DateTime.Now.AddDays(-1)));
searchFilter.Add(new SearchFilter.IsLessThanOrEqualTo(AppointmentSchema.Start, DateTime.Now.AddDays(2)));
ItemView view = new ItemView(50);
view.PropertySet = new PropertySet(BasePropertySet.IdOnly, AppointmentSchema.Subject, AppointmentSchema.Start, AppointmentSchema.AppointmentType, AppointmentSchema.End);
//THIS NEXT LINE!!!
var calendarSearch = new FolderId(WellKnownFolderName.Calendar, new Mailbox("email#ofsomemailbox.com"));
var appointments = _service.FindItems(calendarSearch, searchFilter, view);
I suggest to use impersonation instead of login for each user.
Via impersonation you can impersonate users. Its not the same like full access. Full access is on behave of, impersonation is act as.
A pre of impersonation is you have one username and password instead of having x usernames and passwords.
You can use impersonation like this way:
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010);
service.Credentials = new NetworkCredential(appName, appPassword, emailDomain);
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, userToImpersonate);
when a user has delegate access to someone else, you can access the folder of the other user. For example: Person A will be impersonated and is able to access Person B

Categories

Resources