Unable to send email via Microsoft Graph API (C# Console) - c#

I followed these 2 links to create a console app for sending emails using Graph API:
https://learn.microsoft.com/en-us/graph/api/user-sendmail?view=graph-rest-1.0&tabs=csharp
Microsoft Graph API unable to Send Email C# Console
I have added & granted the required permissions in Azure AD app:
I made sure to provide the client id, tenant id, client secret.
However, I see this error on running the console:
What am I missing?
Here is the code I tried from Microsoft Graph API unable to Send Email C# Console
static void Main(string[] args)
{
// Azure AD APP
string clientId = "<client Key Here>";
string tenantID = "<tenant key here>";
string clientSecret = "<client secret here>";
Task<GraphServiceClient> callTask = Task.Run(() => SendEmail(clientId, tenantID, clientSecret));
// Wait for it to finish
callTask.Wait();
// Get the result
var astr = callTask;
}
public static async Task<GraphServiceClient> SendEmail(string clientId, string tenantID, string clientSecret)
{
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantID)
.WithClientSecret(clientSecret)
.Build();
var authProvider = new ClientCredentialProvider(confidentialClientApplication);
var graphClient = new GraphServiceClient(authProvider);
var message = new Message
{
Subject = "Meet for lunch?",
Body = new ItemBody
{
ContentType = BodyType.Text,
Content = "The new cafeteria is open."
},
ToRecipients = new List<Recipient>()
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "myToEmail#gmail.com"
}
}
},
CcRecipients = new List<Recipient>()
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "myCCEmail#gmail.com"
}
}
}
};
var saveToSentItems = true;
await graphClient.Me
.SendMail(message, saveToSentItems)
.Request()
.PostAsync();
return graphClient;
}

Based on your code which generates the confidentialClientApplication, you are using Client credentials provider.
But the way you send the email is:
await graphClient.Me
.SendMail(message, saveToSentItems)
.Request()
.PostAsync()
It is calling https://graph.microsoft.com/v1.0/me/sendMail in fact.
But Client credentials flow doesn't support /me endpoint. You should call https://graph.microsoft.com/v1.0/users/{id | userPrincipalName}/sendMail endpoint in this case.
So the code should be:
await graphClient.Users["{id or userPrincipalName}"]
.SendMail(message, saveToSentItems)
.Request()
.PostAsync();
Or if you want to use /me/sendMail, choose Authorization code provider, where you should implement interactive login.
You can learn about the scenarios and differences between authorization code flow and client credentials flow.

Related

Adding a Teams Channel Using MS Graph

I am trying to create a channel using MS Graph within a BotFramework bot. I get what appears to be a valid access Token. However the code below generates the following error:
The collection type 'Microsoft.Graph.IChannelMembersCollectionPage' on 'Microsoft.Graph.Channel.Members' is not supported.
var credential = new DefaultAzureCredential();
var token = credential.GetToken(
new Azure.Core.TokenRequestContext(
new[] { "https://graph.microsoft.com/.default" }));
var accessToken = token.Token;
Logger.LogWarning($"Token:{accessToken.ToString()}");
var graphServiceClient = new GraphServiceClient(
new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage
.Headers
.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
return Task.CompletedTask;
}));
try
{
var chan = new Channel
{
DisplayName = $"Chan1",
Description = "This channel is where we debate all future world domination plans",
MembershipType = ChannelMembershipType.Standard
};
await graphServiceClient.Teams["{GroupID}"].Channels.Request().AddAsync(chan);
}
You can use Graph SDK to generate token internally. Please try providing application permissions in azure portal and use the below code to create a channel in MS Teams. Below are the packages you need to install.
This is an example for application permissions. You can try the same code with minor changes/to no changes for delegate permissions.
string clientId = "";
string clientSecret = "1";
string tenantId = "";
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret) // or .WithCertificate(certificate)
.Build();
//AuthorizationCodeProvider authProvider = new AuthorizationCodeProvider(confidentialClientApplication, scopes);
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var channel = new Channel
{
DisplayName = "Topic Discussion",
Description = "This channel is where we debate all future architecture plans",
MembershipType = ChannelMembershipType.Standard
};
await graphClient.Teams["{Your-teams-id}"].Channels
.Request()
.AddAsync(channel);

display list of user in a AD group in azure web app

I am new to azure web app my use case is to display all the users present who belong to a single AD group in a web page. I have already tried to run the power shell command "Get-azureaduser" in my webapp controller but it is throwing me an error stating the "poweshell workspace has to be run in admin mode". Any help is appreciated.
You can try below code snippet using Microsoft Graph SDK
GraphServiceClient graphClient = new GraphServiceClient( authProvider );
var groups = await graphClient.Groups
.Request()
.GetAsync();
Another way you could try:
string tokenUrl = $"https://login.microsoftonline.com/YourTenant.onmicrosoft.com/oauth2/token";
var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenUrl);
//I am Using client_credentials as It is mostly recomended
tokenRequest.Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "client_credentials",
["client_id"] = "b603c7be-a866_Your_Client_Id_6921e61f925",
["client_secret"] = "Vxf1SluKbgu_Client_Secret_SeZ8wL/Yp8ns4sc=",
["resource"] = "https://graph.microsoft.com/" // If you use auth/V2.0 then use ["scope"] = "https://graph.microsoft.com/.default"
});
dynamic json;
AccessTokenClass results = new AccessTokenClass();
HttpClient client = new HttpClient();
var tokenResponse = await client.SendAsync(tokenRequest);
json = await tokenResponse.Content.ReadAsStringAsync();
results = JsonConvert.DeserializeObject<AccessTokenClass>(json);
//New Block For Accessing Group Data from Microsoft Graph Rest API
HttpClient _client = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, string.Format("https://graph.microsoft.com/v1.0/groups"));
//Passing Token For this Request
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", results.access_token);
HttpResponseMessage response = await _client.SendAsync(request);
dynamic objAdGroupList = JsonConvert.DeserializeObject<dynamic>(await response.Content.ReadAsStringAsync());
Class I have used:
public class AccessTokenClass
{
public string token_type { get; set; }
public string expires_in { get; set; }
public string resource { get; set; }
public string access_token { get; set; }
}
Required permission On Azure Portal:
You should have Application permission Group.Read.All,
Directory.Read.All, Group.ReadWrite.AllDirectory.ReadWrite.All
permission on azure portal.
See the screenshot below:
If you still have any concern please refer to official docs and feel free to share.
Hope it would help
You can use the Graph API list group method
GET https://graph.microsoft.com/v1.0/groups
https://learn.microsoft.com/en-us/graph/api/group-list?view=graph-rest-1.0&tabs=http

Sending mail using a daemon application

I am having trouble sending an email from a daemon app. I can get the token with the Client Credential flow but I am unable to send an email with the Microsoft Graph API. I am getting the following error:
Code: BadRequest
Message: Found a function 'microsoft.graph.sendMail' on an open property. Functions on open properties are not supported.
Inner error:
AdditionalData:
request-id: e2e3bb60-2212-4c99-8858-d109aaf4f1cd
date: 2020-01-30T11:18:21
ClientRequestId: e2e3bb60-2212-4c99-8858-d109aaf4f1cd
}
Below is the coding for sending an email through Microsoft Graph.
private readonly IClientCredentialProvider _clientCredentialProvider;
public MailTransmitter()
{
AuthenticationConfig config = AuthenticationConfig.ReadFromJsonFile("appsettings.json"); // contains the tenantId, clientSecret and clientId
_clientCredentialProvider = new ClientCredentialProvider(config);
}
public async Task<bool> SendMail(List<UserEntitlement> sortedListByLastAccessDate)
{
//GraphServiceClient graphClient = new GraphServiceClient(_clientCredentialProvider.GetAuthorizationCodeProvider());
var result = await _clientCredentialProvider.GetClientToken(); // Get token using Client Credentials flow
var accessToken = result.AccessToken;
//should I pass the URL to the graphServiceClient like below? Is the URL right?
var graphServiceClient = new GraphServiceClient("https://graph.microsoft.com/v1.0/0a181b4b-a2fb-4e38-b23b-2c72adc882f2/users/c26d8491-82f8-4f08-990e-35a73ad61ede/memberOf", new DelegateAuthenticationProvider(async (requestMessage) => {
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
}));
var message = new Message
{
Subject = "Meet for lunch?",
Body = new ItemBody
{
ContentType = BodyType.Text,
Content = "The new cafeteria is open."
},
ToRecipients = new List<Recipient>()
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "Bla#hotmail.com"
}
}
},
From = new Recipient
{
EmailAddress = new EmailAddress {
Address = "bla.bla#test.nl"
}
}
};
var saveToSentItems = false;
//Error occurs here
await graphServiceClient.Users["c26d8491-82f8-4f08-990e-35a73ad61ede"]
.SendMail(message, saveToSentItems)
.Request()
.PostAsync();
return true;
}
In case you are wondering how I used the client credentials flow, take a look at: https://github.com/Azure-Samples/active-directory-dotnetcore-daemon-v2/tree/master/1-Call-MSGraph/daemon-console.
What is the problem exactly?
Thanks in advance!
The provided error occurs since invalid url is provided for GraphServiceClient, it expects the first argument to be service root url:
public GraphServiceClient(string baseUrl,IAuthenticationProvider authenticationProvider,IHttpProvider httpProvider = null)
in case of Microsoft Graph API, service root url consist of:
https://graph.microsoft.com is the Microsoft Graph API endpoint.
{version} is the target service version, for example, v1.0 or beta.
for instance, https://graph.microsoft.com/v1.0. Refer Calling the Microsoft Graph API for a details
Example
var app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri(authority))
.Build();
var scopes = new string[] {"https://graph.microsoft.com/.default"};
var result = await app.AcquireTokenForClient(scopes)
.ExecuteAsync();
var client = new GraphServiceClient(
"https://graph.microsoft.com/v1.0",
new DelegateAuthenticationProvider(async (requestMessage) =>
{
requestMessage.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", result.AccessToken);
}));
or via Client credentials provider for that matter:
var app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithClientSecret(clientSecret)
.WithAuthority(new Uri(authority))
.Build();
var authProvider = new ClientCredentialProvider(app);
var client = new GraphServiceClient(authProvider);
Prerequisites: requires Microsoft.Graph.Auth package

Microsoft Graph ASP.NET MVC 5 Send Mail throwing unknown error

Been working on an ASP.NET MVC 5 application that interacts with the MS Graph API.
The controller gets a graph client for the current user and then using that client sends an email.
public class EmailController : Controller
{
// GET: Email
public async Task<ActionResult> Index()
{
var client = await MSGraphServiceClient.GetGraphServiceClientAsync();
await InvitationHelper.SendEmail(client);
return Content("");
}
}
public static async Task<GraphServiceClient> GetGraphServiceClientAsync()
{
string appId = ConfigurationManager.AppSettings["ida:ClientId"];
string appKey = ConfigurationManager.AppSettings["ida:ClientSecret"];
string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
// Get Signed in user
string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
// Get app and user id claims from Azure
string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
// Specify Graph resource URL
string graphResourceID = "https://graph.microsoft.com";
// get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
ClientCredential clientcred = new ClientCredential(appId, appKey);
// initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database
AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance + tenantID, new ADALTokenCache(signedInUserID));
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenSilentAsync(graphResourceID, clientcred, new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
// use delegate to create auth provider using async auth result
var delegateAuthProvider = new DelegateAuthenticationProvider((requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", authenticationResult.AccessToken);
return Task.FromResult(0);
});
// return the graph service client
return new GraphServiceClient(delegateAuthProvider);
}
public static async Task<bool> SendEmail(GraphServiceClient graphServiceClient)
{
var message = new Message()
{
Subject = "Test",
Body = new ItemBody()
{
ContentType = BodyType.Text,
Content = "This is a test"
},
ToRecipients = new List<Recipient>() { new Recipient() { EmailAddress = new EmailAddress() { Address = "jonathan.sweetland#gmail.com" } } }
};
var request = graphServiceClient.Me.SendMail(message, true).Request();
await request.PostAsync();
return true;
}
It is not a permissions error because on AAD I have granted all the permissions for this app. I have other graph calls working so I know it authenticates correctly using the same code.
Used fiddler as was suggested and the JSON response was the same as the error messages displayed.
After a lot of playing around, I found that if you try and use the SendMail on the Graph API whilst authenticated by a Azure Active Directory tenant that is not set up with an outlook account. Even if the account has an outlook account on a different tenant, then you will not be able to use the SendMail endpoint.

How can I get the current user in azure AAD, with ADAL/OpenId?

I have the following code to get the context token.
public class UserProfileController : Controller
{
private static string azureAdGraphApiEndPoint = ConfigurationManager.AppSettings["ida:AzureAdGraphApiEndPoint"];
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
private static string appKey = ConfigurationManager.AppSettings["ida:AppKey"];
public async Task<ActionResult> GetPropertiesForUser()
{
Uri serviceRoot = new Uri(azureAdGraphApiEndPoint);
var token = await GetAppTokenAsync();
ActiveDirectoryClient adClient = new ActiveDirectoryClient(
serviceRoot,
async () => await GetAppTokenAsync());
string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
Microsoft.Azure.ActiveDirectory.GraphClient.Application app = (Microsoft.Azure.ActiveDirectory.GraphClient.Application)adClient.Applications.Where(
a => a.AppId == clientId).ExecuteSingleAsync().Result;
if (app == null)
{
throw new ApplicationException("Unable to get a reference to application in Azure AD.");
}
string requestUrl = string.Format("https://graph.windows.net/mysaasapp.onmicrosoft.com/users/{0}?api-version=1.5", token);
HttpClient hc = new HttpClient();
hc.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", token);
HttpResponseMessage hrm = await hc.GetAsync(new Uri(requestUrl));
if (hrm.IsSuccessStatusCode)
{
string jsonresult = await hrm.Content.ReadAsStringAsync();
return View("TestRestCall", new SuccessViewModel
{
Name = "The Title",
Message = "The message",
JSON = jsonresult.ToJson()
});
}
else
{
return View();
}
}
private static async Task<string> GetAppTokenAsync()
{
string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
string appKey = ConfigurationManager.AppSettings["ida:AppKey"];
string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
string postLogoutRedirectUri = ConfigurationManager.AppSettings["ida:PostLogoutRedirectUri"];
string azureAdGraphApiEndPoint = ConfigurationManager.AppSettings["ida:AzureAdGraphApiEndPoint"];
// This is the resource ID of the AAD Graph API. We'll need this to request a token to call the Graph API.
string graphResourceId = ConfigurationManager.AppSettings["ida:GraphResourceId"];
string Authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
// Instantiate an AuthenticationContext for my directory (see authString above).
AuthenticationContext authenticationContext = new AuthenticationContext(Authority, false);
// Create a ClientCredential that will be used for authentication.
// This is where the Client ID and Key/Secret from the Azure Management Portal is used.
ClientCredential clientCred = new ClientCredential(clientId, appKey);
// Acquire an access token from Azure AD to access the Azure AD Graph (the resource)
// using the Client ID and Key/Secret as credentials.
AuthenticationResult authenticationResult = await authenticationContext.AcquireTokenAsync(graphResourceId, clientCred);
// Return the access token.
return authenticationResult.AccessToken;
}
However I need to replace the token with the current user email, but havent found how.
First of all, forgive me if I've misunderstood the problem.
If you want to get the user's email from Claims, I think that can be somewhat different depending on your tenant. For me, I can find my email i Claims under both "upn" and "unique name".
So as an example;
string email= ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn) != null ? ClaimsPrincipal.Current.FindFirst(ClaimTypes.Upn).Value : ClaimsPrincipal.Current.FindFirst(ClaimTypes.Email).Value;
This would give me my email, or null.
In my case, if I use, as you do, ""http://schemas.microsoft.com/identity/claims/objectidentifier"", that returns a GUID uniquely identifying me in my tenants Active Directory.
Have you inspected your Claims to see what is in there?

Categories