Use the Outlook/Office365 REST API to send mail from connected email address - outlook-restapi

I am trying to send an email using the Outlook/Office 365 REST API, and I am trying to send it as an address that I have as a "Connected Account". Attempting to send the message returns a `` error. However, the API will let me create a draft with this address.
Additionally, I can send the API-created draft just fine, and I can also create and send messages as this account from the web interface.
Is there a way to authorize the API to be able to send a message as an address for a connected account?

No, the API doesn't support this today. It has to do with the scope of the permissions that you consent to. "Allow this app to send mail as you" covers sending from your account, but not from another account, even if you have been granted access.

Another thing you can think about is to leverage App-only authentication. You can configure a Azure AD App to have App-only authentication. After that, all the request will behalf of that app id and you should be able to delegate that app id to send email to anyone on behalf of the user you want.
The following is the steps:
Create a Azure AD application.
Configure your Azure AD Application to allow App-only token by
following Build service and daemon apps in Office 365. You also
need a certificate for app-only token request.
Go to your Azure AD Application->Configuration, Click "Add
application" to add "Office 365 Exchange Online". Select "Send email
as any user" under "Application permission" dropdown box. It allows
your Azure AD App to have permission to send email on behalf of
someone.
Once you have Azure AD application configured, you can refer the
following code for sending email on behalf of a specific user.
string tenantId = "yourtenant.onmicrosoft.com";
string clientId = "your client id";
string resourceId = "https://outlook.office.com/";
string resourceUrl = "https://outlook.office.com/api/v2.0/users/service#contoso.com/sendmail"; //this is your on behalf user's UPN
string authority = String.Format("https://login.windows.net/{1}", AUTHORITYURL, tenantId);
string certificatPath = #"c:\test.pfx"; //this is your certficate location.
string certificatePassword = "xxxx"; // this is your certificate password
var itemPayload = new
{
Message = new
{
Subject = "Test email",
Body = new { ContentType = "Text", Content = "this is test email." },
ToRecipients = new[] { new { EmailAddress = new { Address = "test#cotoso.com" } } }
}
};
//if you need to load from certficate store, use different constructors.
X509Certificate2 certificate = new X509Certificate2(certficatePath, certificatePassword, X509KeyStorageFlags.MachineKeySet);
AuthenticationContext authenticationContext = new AuthenticationContext(authority, false);
ClientAssertionCertificate cac = new ClientAssertionCertificate(clientId, certificate);
//get the access token to Outlook using the ClientAssertionCertificate
var authenticationResult = await authenticationContext.AcquireTokenAsync(resourceId, cac);
string token = authenticationResult.AccessToken;
//initialize HttpClient for REST call
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + token);
client.DefaultRequestHeaders.Add("Accept", "application/json");
//setup the client post
HttpContent content = new StringContent(JsonConvert.SerializeObject(itemPayload));
//Specify the content type.
content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json;odata=verbose");
HttpResponseMessage result = await client.PostAsync(url, content);
if(result.IsSuccessStatusCode)
{
//email send successfully.
}else
{
//email send failed. check the result for detail information from REST api.
}
For a complete explanation, please reference to my blog Send email on behalf of a service account using Office Graph API
I hope it helps and let me know if you have questions.

Related

Token management for ClientSecretCredential usage in MSGraph API

I need to send mails from a background application(worker service/azure function) using MSGraph API with Application permission. This is how I have initialized the GraphServiceClient to send emails.
var credentials = new ClientSecretCredential(
"TenantID",
"ClientId",
"ClientSecret",
new TokenCredentialOptions { AuthorityHost = AzureAuthorityHosts.AzurePublicCloud });
GraphServiceClient graphServiceClient = new GraphServiceClient(credentials);
var subject = $"Subject from demo code";
var body = $"Body from demo code";
// Define a simple e-mail message.
var email = new Microsoft.Graph.Message
{
Subject = subject,
Body = new ItemBody
{
ContentType = BodyType.Html,
Content = body
},
ToRecipients = new List<Recipient>()
{
new Recipient { EmailAddress = new EmailAddress { Address = "EmailId" }}
}
};
// Send mail as the given user.
graphServiceClient
.Users["UserID"]
.SendMail(email, true)
.Request()
.PostAsync().Wait();
How long the graphServiceClient will have a valid token and how to regenerate a token when the token is expired.
What are the best practices for this usage
The default lifetime of an access token is variable. When issued, an access token's default lifetime is assigned a random value ranging between 60-90 minutes (75 minutes on average). Please refer this DOC for more information.
The refresh tokens can be used to acquire access tokens if they expire, Refresh tokens are long-lived, and can be used to retain access to resources for extended periods of time.
For getting the access tokens with client secret please refer this method of getting the access tokens.
Hope this will help.
As of May 2022, there is no option to generate a refresh token while using client_credentials options.
For client_credentials option
It's possible to generate a refresh token while using on behalf of user
For on behalf of a user

How to query MS Graph API in User Context?

I'm trying to change a user's password using MS Graph API. I was checking earlier questions like this and this where the answer were always similar: register an AAD application, because changing the password requires Delegated
UserAuthenticationMethod.ReadWrite.All permissions, and you cannot set that in a B2C application as a B2C app supports only offline_access and openid for Delegated.
So the answers were always suggesting creating an AAD app, and using this app I could query the Graph API on behalf of the user. The question is, how to achieve this? If I check the documentation from Microsoft: Get access on behalf of a user, it is saying that first you need to get authorization, only then you can proceed to get your access token.
But as part of the authorization process, there is a user consent screen. If I'm calling my ASP.NET Core Web API endpoint to change my password on behalf of my user, how will it work on the server? The client won't be able to consent, if I'm doing these calls on the server, right?
Also, I'm using Microsoft.Graph and Microsoft.Graph.Auth Nuget packages and it's not clear how to perform these calls on behalf of the user. I was trying to do this:
var client = new GraphServiceClient(new SimpleAuthProvider(authToken));
await client.Users[myUserId]
.ChangePassword(currentPassword, newPassword)
.Request()
.PostAsync();
Where SimpleAuthProvider is just a dummy IAuthProvider implementation.
Any ideas how to make this work?
OK, got it:
static void ChangePasswordOfAUser()
{
var myAzureId = "65e328e8-5017-4966-93f0-b651d5261e2c"; // id of B2C user
var currentPassword = "my_old_pwd";
var newPassword = "newPassword!";
using (var client = new HttpClient())
{
var passwordTokenRequest = new PasswordTokenRequest
{
Address = $"https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token",
ClientId = clientId, // client ID of AAD app - not the B2C app!
ClientSecret = clientSecret,
UserName = $"{myAzureId}#contoso.onmicrosoft.com",
Password = currentPassword,
Scope = "https://graph.microsoft.com/.default" // you need to have delegate access
};
var response = client.RequestPasswordTokenAsync(passwordTokenRequest).Result;
var userAccessToken = response.AccessToken;
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {userAccessToken}");
var json = System.Text.Json.JsonSerializer.Serialize(new
{
currentPassword = currentPassword,
newPassword = newPassword
});
var changePasswordResponse = client.PostAsync(
$"https://graph.microsoft.com/v1.0/users/{myAzureId}/changePassword",
new StringContent(json, Encoding.UTF8, "application/json"))
.Result;
changePasswordResponse.EnsureSuccessStatusCode();
}
}

How do I Call Microsoft Teams OnlineMeeting endpoints via Microsoft Graph API using a console app?

I have followed the code example given in the following link by Microsoft and was successfully able to get the list of users.
My registered app in the Azure Active Directory also have the "OnlineMeeting.ReadWrite.All" application permission.
But when I am trying to call the create meeting call by posting the request in the endpoint "https://graph.microsoft.com/v1.0/me/onlineMeetings". I am getting a 403 forbidden error. Any idea why I am getting this?
For the graph api create online meetings https://graph.microsoft.com/v1.0/me/onlineMeetings, we can see the tutorial shows it doesn't support "Application permission" to call it. It just support "Delegated permission", so we can just request it by password grant flow but not client credential flow.
Update:
For your requirement to request the graph api of creating online meeting, we can just use password grant flow or auth code flow. Here provide a sample of password grant flow(username and password) for your reference, use this sample to get the token and request the graph api by this token. You can also find this sample in this tutorial.
static async Task GetATokenForGraph()
{
string authority = "https://login.microsoftonline.com/contoso.com";
string[] scopes = new string[] { "user.read" };
IPublicClientApplication app;
app = PublicClientApplicationBuilder.Create(clientId)
.WithAuthority(authority)
.Build();
var accounts = await app.GetAccountsAsync();
AuthenticationResult result = null;
if (accounts.Any())
{
result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
else
{
try
{
var securePassword = new SecureString();
foreach (char c in "dummy") // you should fetch the password
securePassword.AppendChar(c); // keystroke by keystroke
result = await app.AcquireTokenByUsernamePassword(scopes,
"joe#contoso.com",
securePassword)
.ExecuteAsync();
}
catch(MsalException)
{
// See details below
}
}
Console.WriteLine(result.Account.Username);
}

Facing "The token contains no permissions, or permissions can not be understood." issue in EWS with Oauth

We have requirement in which we need to add appointment to user outlook account on behalf of that user using delegate access. All mails are in same domain or same network. We are trying this by using Oauth with office365, by creating Azure application and providing application level "calendar read and write" delegate permission.
I have referred https://blogs.msdn.microsoft.com/exchangedev/2015/01/21/building-daemon-or-service-apps-with-office-365-mail-calendar-and-contacts-apis-oauth2-client-credential-flow/ article to setup azure application, which will provide me a access token.
While fetching access token I have used following code
private static string GetTokenUsingCertificate()
{
string authority = string.Format("https://login.windows.net/{0}/oauth2/authorize", tenantID); ;
string outlookUri = "https://outlook.office365.com/";
var authenticationContext = new AuthenticationContext(authority, false);
var clientCertificate = new ClientAssertionCertificate(clientId, GetClientCertificate());
AuthenticationResult authenticationResult = null;
try
{
authenticationResult = authenticationContext.AcquireTokenAsync(outlookUri, clientCertificate).Result;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return authenticationResult?.AccessToken;
}
above function is providing me access token but when I try to add appointment to user outlook calendar using Exchnage Service, I am getting "The token contains no permissions, or permissions can not be understood." exception.
While adding appointment I am using following code
private void AddAppointment()
{
ExchangeService exchangeService = new ExchangeService();
exchangeService.Credentials = new OAuthCredentials(accessToken);
exchangeService.Url = new Uri("https://outlook.office365.com/EWS/Exchange.asmx");
Appointment appointment = new Appointment(service)
{
Subject = "EWS OAuth: Appointment 2",
Body = "EWS OAuth Auth: Body",
Start = DateTime.Now.AddMinutes(10)
};
appointment.Save(
new FolderId(WellKnownFolderName.Calendar, new Mailbox("mail#outlook.com/")),
SendInvitationsMode.SendToNone);
}
Please help me to resolve this issue.
Blockquote
EWS doesn't allow the same level of OAuth permissions scope (or permission restriction) that the REST API allows for which is one of the big benefit of using REST vs EWS. If you have Office365 why are trying to use EWS over REST ?
To answer the question you will need
OAuth authentication for EWS is only available in Exchange as part of Office 365. EWS applications require the "Full access to user's mailbox" permission.
as per https://msdn.microsoft.com/en-us/library/office/dn903761%28v=exchg.150%29.aspx .

How to call Microsoft Graph from console application c#

I need to call Microsoft Graph API to create user in Azure AD.
First I need to test from console application and then need to implement in Azure function.
https://developer.microsoft.com/en-us/graph/graph-explorer
I am new to Microsoft Graph API , How can I connect and execute API from c# console application.
I have already registered the application in AAD.
I am trying to acquire token as :
string resourceId = "https://graph.microsoft.com";
string tenantId = "<tenantID>";
string authString = "https://login.microsoftonline.com/" + tenantId;
string upn = String.Empty;
string clientId = "<ClientID>";
string clientSecret = "<clientSecret>";
//string clientSecret = ConfigurationManager.AppSettings["clientSecret"];
log.Verbose("ClientSecret=" + clientSecret);
log.Verbose("authString=" + authString);
var authenticationContext = new AuthenticationContext(authString, false);
// Config for OAuth client credentials
ClientCredential clientCred = new ClientCredential(clientId, clientSecret);
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(resourceId,clientCred);
string token = authenticationResult.AccessToken;
log.Verbose("token=" + token);
I trying to use existing AADB2C.
b2c-extensions-app. Do not modify. Used by AADB2C for storing user data.
I have enabled permission as:
I neither get exception nor get access token and program silently exit
Also :
There is new library
<package id="Microsoft.Identity.Client" version="1.1.0-preview" targetFramework="net46" />
How can I direct login without login pop-up with the following and acquire token ?
PublicClientApplication
I assume that you already have Azure AD application with granted Administrative Consent.
In order to connect from a console app, you'll need to first obtain a valid token. Since you lack a UI, you'll want to Get access without a user. Note that this type of "app-only" token requires Administrative Consent before it can be used.
Then you have to add two NuGet dependencies to your dotnet project
<PackageReference Include="Microsoft.Graph" Version="1.15.0" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.0.0" />
Microsoft.Identity.Client for authentication using Azure AD and Microsoft.Graph for executing MS Graph queries.
var tenantId = "you-azure-tenand-id";
var clientId = "azure-ad-application-id";
var clientSecret = "unique-secret-generated-for-this-console-app";
// Configure app builder
var authority = $"https://login.microsoftonline.com/{tenantId}";
var app = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri(authority))
.Build();
// Acquire tokens for Graph API
var scopes = new[] {"https://graph.microsoft.com/.default"};
var authenticationResult = await app.AcquireTokenForClient(scopes).ExecuteAsync();
// Create GraphClient and attach auth header to all request (acquired on previous step)
var graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(requestMessage => {
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("bearer", authenticationResult.AccessToken);
return Task.FromResult(0);
}));
// Call Graph API
var user = await graphClient.Users["Me#domain.com"].Request().GetAsync()
Update 2020.01
There is a new package Microsoft.Graph.Auth that simplify auth and token management.
Let's say you want to use some Beta API this time.
<PackageReference Include="Microsoft.Graph.Auth" Version="1.0.0-preview.2" />
<PackageReference Include="Microsoft.Graph.Beta" Version="0.12.0-preview" />
var tenantId = "you-azure-tenand-id";
var clientId = "azure-ad-application-id";
var clientSecret = "unique-secret-generated-for-this-console-app";
// Configure application
var clientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
// Create ClientCredentialProvider that will manage auth token for you
var authenticationProvider = new ClientCredentialProvider(clientApplication);
var graphClient = new GraphServiceClient(authenticationProvider);
// Call Graph API
var user = await graphClient.Users["Me#domain.com"].Request().GetAsync()
In order to connect from a console app, you'll need to first obtain a valid token. Since you lack a UI, you'll want to Get access without a user. Note that this type of "app-only" token requires Administrative Consent before it can be used.
In order to support the Create User scenario, you will need to ensure your permission scopes include User.ReadWrite.All.
Once you have a valid token you can make calls into the Graph API. Graph is a REST API so all calls are made over HTTP with the token passed within the Authorization Header.
You can read a general overview at Get started with Microsoft Graph and REST. There are also several language/framework specific overviews available but all of them assume you have a UI (i.e. not simply console). Generally speaking, if you're looking for a console tool for creating users you may prefer using PowerShell.
This question is rather old, but it was one of the first questions that popped up when I initially needed to do the same thing. Below I will document the steps and resources I used to make it happen:
I used an O365 tenant (you can get one from office.com - note that you can get a one year developer trial). Once you have a tenant, you also have access to Azure portal if you log in as your tenant admin user. Under Azure Portal, go to Active Directory/properties to see the tenant ID.
I followed the instructions here https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-netcore-daemon to create a new registered application. I created a new secret and copied the value (that will be client secret in your console app). The registered application id will be the client ID in your console app.
I cloned the github repo in the above link and changed the values in the appsettings to the tenant ID, client ID, and client secret noted in the steps above.
The code in that repo has some methods called which no longer exist in ConfigurationBuilder as of .NETCore 2.1. I substituted these lines (there's probably a better / shorter way):
authenticationConfig.Tenant = Configuration.GetSection("Tenant").Value.ToString();
authenticationConfig.ClientId = Configuration.GetSection("ClientId").Value.ToString();
authenticationConfig.ClientSecret = Configuration.GetSection("ClientSecret").Value.ToString();
You should now be iterating through users in your tenant. You can go to the graph explorer ( https://developer.microsoft.com/en-us/graph/graph-explorer ) to find more URLs (find the line in Program.cs to substitute them). As far as I know so far, v2.0 of the API is "beta" (put "beta" where "v1.0" is - someone please correct me if I'm wrong).
await apiCaller.CallWebApiAndProcessResultASync("https://graph.microsoft.com/v1.0/users", result.AccessToken, Display);
This MSAL console app tutorial describes getting a token using MSAL (Microsoft Authentication Library) in a .NET console app.
To make a Microsoft Graph call, I replaced the RunAsync() function
with this, which attaches the acquired token to the requests with the
GraphServiceClient:
static async Task RunAsync()
{
const string clientId = "your client id";
string[] scopes = { "User.Read" };
AuthenticationResult result;
var clientApp = new PublicClientApplication(clientId);
try
{
result = await clientApp.AcquireTokenAsync(scopes.Split(new char[] { ' ' }));
Console.WriteLine(result.AccessToken);
GraphServiceClient graphClient = new GraphServiceClient(
new DelegateAuthenticationProvider(
async (requestMessage) =>
{
// Append the access token to the request.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", result.AccessToken);
// Some identifying header
requestMessage.Headers.Add("SampleID", "aspnet-connect-sample");
}));
// Get a page of mail from the inbox
var inboxMail = await graphClient.Me.MailFolders.Inbox.Messages.Request().GetAsync();
foreach(var mail in inboxMail.CurrentPage.ToList())
{
Console.Write("From: {0}\nSubject: {1}\nBody:\n{2}\n--------------------\n",
mail.From.EmailAddress.Address, mail.Subject, mail.BodyPreview);
}
}
// Unable to retrieve the access token silently.
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}

Resources