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.
Related
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.
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?
This seem to be a pretty easy one, but as I dont have exp with async too much, then I come to the experts.
I need to make a rest call, and for it I need an authorization token.(bearer token)
This is already done by this:
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, from the caller method, which is a lambda expression with await, I dont know how to get a reference to that result and put in the httpheader
public ActionResult TestRestCall()
{
Uri serviceRoot = new Uri(azureAdGraphApiEndPoint);
ActiveDirectoryClient adClient = new ActiveDirectoryClient(
serviceRoot,
async () => await GetAppTokenAsync());
Application app = (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.");
}
HttpClient hc = new HttpClient();
hc.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", CODE HERE);
}
You should make your caller function Async like:
public async Task<ActionResult> TestRestCall()
then call your async method:
var token = await GetAppTokenAsync();
ActiveDirectoryClient adClient = new ActiveDirectoryClient(
serviceRoot, token);
I'm trying to figure out how to delete an AppRoleAssignment from either an Group or a User using the Graph API for Azure Active Directory. I'm using the .NET SDK (Microsoft.Azure.ActiveDirectory.GraphClient).
I've tried using the standard DeleteAsync method that's on every IEntityBase, but it fails with an error. It's issuing an HTTP request that looks like this:
DELETE /{tenantId}/directoryObjects/{appRoleAssignment ObjectID}/Microsoft.DirectoryServices.AppRoleAssignment?api-version=1.5
which fails with a 400 Bad Request with the error "Direct queries to this resource type are not supported."
This isn't the correct way to delete AppRoleAssignments using the Graph API according to this Microsoft blog post which says you need to do an HTTP request that looks like:
DELETE /{tenantId}/users/{user object ID}/appRoleAssignments/{appRoleAs}?api-version=1.5
If I do a manual HTTP request using HttpClient using that URL format, it works, but I want to know how to do this within the bounds of the .NET library rather than doing manual HTTP requests myself.
How do I delete AppRoleAssignments via the .NET library?
While it is not fixed, you can make a manual HTTP-request, but still using Azure AD SDK to acqure the token. Something like this:
var tenantId = "<guid> tenant id";
var appId = "<guid> your Azure app id";
var appKey = "your app key";
var authority = "i.e. https://login.windows.net/mycompany.onmicrosoft.com";
var graphUrl = "https://graph.windows.net/";
public async Task RemoveRoleFromUser(Guid userId, string roleObjectId) {
var uri = string.Format("{0}/users/{1}/appRoleAssignments/{2}?api-version=1.5", tenantId, userId, roleObjectId);
await ExecuteRequest<object>(uri, HttpMethod.Delete);
}
private async Task<T> ExecuteRequest<T>(string uri, HttpMethod method = null, Object body = null) where T : class {
if (method == null) method = HttpMethod.Get;
T response;
var token = await AcquireTokenAsyncForApplication();
using (var httpClient = new HttpClient { BaseAddress = getServicePointUri() }) {
var request = new HttpRequestMessage(method, uri);
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
if (body != null) {
request.Content = new StringContent(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
}
var responseMessage = await httpClient.SendAsync(request).ConfigureAwait(false);
responseMessage.EnsureSuccessStatusCode();
response = await responseMessage.Content.ReadAsAsync<T>();
}
return response;
}
private async Task<string> AcquireTokenAsyncForApplication() {
ClientCredential clientCred = new ClientCredential(appId, appKey);
var authenticationContext = new AuthenticationContext(authority, false);
AuthenticationResult authenticationResult = authenticationContext.AcquireToken(graphUrl, clientCred);
return authenticationResult.AccessToken;
}
private Uri getServicePointUri() {
Uri servicePointUri = new Uri(graphUrl);
Uri serviceRoot = new Uri(servicePointUri, tenantId);
return serviceRoot;
}
ActiveDirectoryClient client = AuthenticationHelper.GetActiveDirectoryClient();
user = (User) await client.Users.GetByObjectId(objectId).ExecuteAsync();
var roleId = "";
await user.AppRoleAssignments.Where(t=>t.ObjectId==roleId).FirstOrDefault().DeleteAsync();
The following websites might be helpful:
https://github.com/AzureADSamples/WebApp-RoleClaims-DotNet
https://github.com/AzureADSamples/WebApp-GraphAPI-DotNet
I am developing social networks integration for my asp.net mvc4 application.
Twitter and Facebook were very easy for me but I am seriously stuck with LinkedIn.
Here is my code.
public ActionResult LinkedInTest(string text)
{
var client = new RestClient
{
Authority = "https://api.linkedin.com/uas/oauth",
Credentials = LinkedInSocialHelper.GetCredentials()
};
var request = new RestRequest {Path = "requestToken"};
RestResponse response = client.Request(request);
token = response.Content.Split('&')[0].Split('=')[1];
tokenSecret = response.Content.Split('&')[1].Split('=')[1];
textToPost = text;
Response.Redirect("https://api.linkedin.com/uas/oauth/authorize?oauth_token=" + token + "&scope=r_basicprofile+r_emailaddress+r_network+r_contactinfo+rw_nus");
return null;
textToPost = text;
return RedirectToAction("LinkedInCallback");
}
public ActionResult LinkedInCallback()
{
verifier = Request["oauth_verifier"];
var client = new RestClient
{
Authority = "https://api.linkedin.com/uas/oauth",
Credentials = LinkedInSocialHelper.GetCredentials(token, tokenSecret, verifier),
Method = WebMethod.Post
};
var request = new RestRequest {Path = "accessToken"};
RestResponse response = client.Request(request);
token = response.Content.Split('&')[0].Split('=')[1];
tokenSecret = response.Content.Split('&')[1].Split('=')[1];
LinkedInSocialHelper.Post(textToPost, token, tokenSecret);
return RedirectToAction("Calendar");
}
public static void Post(string text, string accessToken, string accessTokenSecret)
{
var tokenManager = new TokenManager(ApiKey, ApiSecret);
tokenManager.ExpireRequestTokenAndStoreNewAccessToken(null, null, accessToken, accessTokenSecret);
var authorization = new WebOAuthAuthorization(tokenManager, UserToken);
LinkedInService service = new LinkedInService(authorization);
//var user = service.GetCurrentUser(ProfileType.Public); - IT IS GIVING ME THE SAME ERROR - Access denied
service.CreateShare(text, VisibilityCode.ConnectionsOnly);
}
Everything works fine except last thing - posting shares - I get Access to posting shares denied exception despite the fact that I generate token using all the necessary permissions:
"https://api.linkedin.com/uas/oauth/authorize?oauth_token=" + token + "&scope=r_basicprofile+r_emailaddress+r_network+r_contactinfo+rw_nus"
Hope you good guys help me.
See the last post here - it describes how to solve it
https://developer.linkedin.com/forum/permission-scope-request-token-query-not-working?page=1