I'm working with Microsoft.Graph SDK and I need to get email SentItems programmatically in a class library.
I'm using the following code to create a client:
private static Graph.GraphServiceClient CreateClient()
{
var scopes = new[] { "User.Read" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "xxx";
// Value from app registration
var clientId = "xxxx";
var pca = Microsoft.Identity.Client.PublicClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.Build();
// DelegateAuthenticationProvider is a simple auth provider implementation
// that allows you to define an async function to retrieve a token
// Alternatively, you can create a class that implements IAuthenticationProvider
// for more complex scenarios
var authProvider = new Graph.DelegateAuthenticationProvider(async (request) =>
{
// Use Microsoft.Identity.Client to retrieve token
var result = await pca.AcquireTokenByIntegratedWindowsAuth(scopes).ExecuteAsync();
request.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", result.AccessToken);
});
return new Graph.GraphServiceClient(authProvider);
}
then I'm trying to use the client the next waay:
var sentEmails = graphClient.Users[authMail].MailFolders.SentItems.Request().GetAsync().Result;
but I'm getting the following exception when executing the request:
Exception thrown: 'Microsoft.Identity.Client.MsalUiRequiredException'
in System.Private.CoreLib.dll Exception thrown:
'System.AggregateException' in System.Private.CoreLib.dll
I thought that another option could be to get an auth token. I can get an auth token with the next code:
private static async Task<string> GetGraphToken()
{
var resource = "https://graph.microsoft.com/";
var instance = "https://login.microsoftonline.com/";
var tenant = "xxx";
var clientID = "xxxx";
var secret = "xxxxx";
var authority = $"{instance}{tenant}";
var authContext = new AuthenticationContext(authority);
var credentials = new ClientCredential(clientID, secret);
var authResult = authContext.AcquireTokenAsync(resource, credentials).Result;
return authResult.AccessToken;
}
And it just works, but then I don't know how to use it to do an API request programmatically.
Any of the two variants is OK for me, getting rid of the exceptions in the first case, or finding a way to use the token to make a programmatic SDK API call in the second.
What can I try next?
Edit 1
I'm trying with the next approach, but the same exception is thrown:
var accessToken = GetToken();
var client = new Graph.GraphServiceClient(
new Graph.DelegateAuthenticationProvider(
(requestMessage) =>
{
requestMessage.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
return Task.FromResult(0);
}));
var mails = client.Users[authMail].MailFolders.SentItems.Messages.Request().GetAsync().Result;
Pls go to the api document to check the api permission required. For example, this api required Mail.ReadBasic.All, Mail.Read, Mail.ReadWrite for application type. My code sample requires to use application type of api permission.
Pls consent the api permission and try code below:
using Microsoft.Graph;
using Azure.Identity;
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "aad_app_id";
var clientSecret = "client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var res = await graphClient.Users["{user-id}"].MailFolders.SentItems.Request().GetAsync();
In your first example you trying to use IWA auth https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-acquire-token-integrated-windows-authentication?tabs=dotnet and it failing because there is interaction required. Most likely this is due to MFA being enabled on the account, generally you don't want to disable MFA so you either need to deal with the interaction and perform the other factor or use another method. You also don't have the correct scope for email eg Mail.Read would be required for
In the second method your using client credentials flow (but the older v1 flow) but if you want to use the Graph SDK it easier to just do https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS#client-credentials-provider but make sure you have the correct permission in your app registration and make sure its been consented to.
Related
I have a mobile app, which needs to call a REST API. Here is my code:
string url = $#"https://graph.microsoft.com/v1.0/solutions/bookingBusinesses/{adTenantId}/appointments";
string accessToken = new JwtSecurityTokenHandler().WriteToken(AuthService.JwtToken);
HttpClient client = new();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
HttpResponseMessage response = await client.GetAsync(url).ConfigureAwait(false);
This gives me error 401 (Unauthorized). I may be wrong, but it seems to me that it is because I use the access token based on the user's authentication. I probably need to call the API not as the user, but as my app. Buy I don't know how to get an access token for the app.
The app is registered with Azure AD and has necessary API permissions.
Assume you have set up the app for MS Graph API correctly, and you have the below configurations:
ClientId
ClientSecret
TenantId
Scopes
var app = ConfidentialClientApplicationBuilder.Create(ClientId)
.WithClientSecret(ClientSecret)
.WithAuthority(new Uri("https://login.microsoftonline.com/" + Tenant))
.Build();
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
var accessToken = result.AccessToken;
Note that I use scope https://graph.microsoft.com/.default for all the permissions you have assigned to the application. You could use more specific scopes.
References.
I have created an ASP.NET Framework application using Microsoft Identify Platform from the standard template and used ConfidentialClient to acquire an access token. I now want to use this access token to call the Azure DevOps REST API.
My scenario is:
Open the application and immediately get asked to log in
Acquire an access token from ConfidentialClient
Execute an API call to Azure DevOps (e.g. GET https://dev.azure.com/{organization}/_apis/projects)
I believe I have completed steps 1 and 2 (code below), but when I execute the API is doesn't return the results, merely a HTML page asking me to login
The access token is recovered from the following code:
private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedNotification context)
{
var authCode = context.Code;
var tenantId = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
var authority = aadInstance + tenantId;
//string[] scopes = new string[] { "https://graph.microsoft.com/User.Read" };
string[] scopes = new string[] { "https://app.vssps.visualstudio.com/user_impersonation" };
//string[] scopes = new string[] { "https://graph.microsoft.com/User.Read", "https://app.vssps.visualstudio.com/user_impersonation" };
// Get the access token from the ConfidentialClientApplication)
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create(clientId)
.WithRedirectUri(redirectUri)
.WithClientSecret(clientSecret)
.WithAuthority(authority)
.Build();
var authResult = await app.AcquireTokenByAuthorizationCode(scopes, authCode).ExecuteAsync();
string accessToken = authResult.AccessToken;
Debug.WriteLine($"Access Token: {accessToken}");
//await GetProfileData(accessToken);
await GetProjectList(accessToken);
}
If I run this I get the access token but using this as the bearer token in my API call doesn't work. The method for calling the API is as follows:
private async Task GetProjectList(string accessToken)
{
// Get the Project List from the Azure DevOps API
var httpClient = new HttpClient();
var httpRequest = new HttpRequestMessage(HttpMethod.Get,
"https://dev.azure.com/gp-ementris/_apis/projects");
httpRequest.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", accessToken);
var response = await httpClient.SendAsync(httpRequest);
if (response.IsSuccessStatusCode)
{
Debug.WriteLine(await response.Content.ReadAsStringAsync());
}
}
Can someone help explain how I can get the API to work with the token?
Thanks
This question already has answers here:
Microsoft Graph api code in C# displays only limited number of users
(2 answers)
Closed 3 years ago.
I am running below code :
using Microsoft.Graph;
using Microsoft.Identity.Client;
using System;
namespace MSGraphAPI
{
class Program
{
private static string clientId = "XXXXX";
private static string tenantID = "XXXX";
private static string objectId = "XXXX";
private static string clientSecret = "XXXX";
static async System.Threading.Tasks.Task Main(string[] args)
{
// IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
//.Create(clientId)
//.WithTenantId(tenantID)
//.WithClientSecret(clientSecret)
//.Build();
// ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
// GraphServiceClient graphClient = new GraphServiceClient(authProvider);
// var users = await graphClient.Users
// .Request()
// .GetAsync();
var tenantId = "XXXX.onmicrosoft.com";
// The client ID of the app registered in Azure AD
var clientId = "XXXX";
// *Never* include client secrets in source code!
var clientSecret = "XXXX"; // Or some other secure place.
// The app registration should be configured to require access to permissions
// sufficient for the Microsoft Graph API calls the app will be making, and
// those permissions should be granted by a tenant administrator.
var scopes = new string[] { "https://graph.microsoft.com/.default" };
// Configure the MSAL client as a confidential client
var confidentialClient = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithAuthority($"https://login.microsoftonline.com/XXXXX.onmicrosoft.com/v2.0")
.WithClientSecret(clientSecret)
.Build();
// Build the Microsoft Graph client. As the authentication provider, set an async lambda
// which uses the MSAL client to obtain an app-only access token to Microsoft Graph,
// and inserts this access token in the Authorization header of each API request.
GraphServiceClient graphServiceClient =
new GraphServiceClient(new DelegateAuthenticationProvider(async (requestMessage) => {
// Retrieve an access token for Microsoft Graph (gets a fresh token if needed).
var authResult = await confidentialClient
.AcquireTokenForClient(scopes)
.ExecuteAsync();
// Add the access token in the Authorization header of the API request.
requestMessage.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", authResult.AccessToken);
})
);
// Make a Microsoft Graph API query
var users = await graphServiceClient.Users.Request().GetAsync();
Console.WriteLine(users.ToString());
IGraphServiceUsersCollectionPage userss = graphServiceClient.Users.Request().GetAsync().Result;
foreach (User user in userss)
{
Console.WriteLine("Found user: " + user.DisplayName);
}
}
}
}
I am getting only 100 users ( when i use debug and check the count and also put a watch on this variable) in the userss variable , but total number of users is around 1000 . I want to fetch all user details and also want to fetch users based on specific criteria using select or any other api in the same code.
Please help me.
Below code works to show all user details.
do
{
foreach (User user in users)
{
if (user.Mail != null)
{
if (user.Mail.Contains("Tom"))
{
Console.WriteLine("Hurray");
}
}
Console.WriteLine($"{user.Id}");
Flag++;
}
}
while (users.NextPageRequest != null && (users = await users.NextPageRequest.GetAsync()).Count > 0);
I'm having trouble when using the Microsoft Graph API. Whenever I try to get a calendar, I get the following error message:
Exception thrown: 'Microsoft.Graph.ServiceException' in
System.Private.CoreLib.dll: 'Code: BadRequest Message: Current
authenticated context is not valid for this request
At first, I thought it was similar to this post, but my user is authenticated, so I believe it's not the case.
Here's my code:
EventController.cs
public async Task<Calendar> GetEventInfoAsync()
{
var accessToken = await getAcessTokenAsync();
DelegateAuthenticationProvider delegateAuthenticationProvider = new DelegateAuthenticationProvider(
(requestMessage) =>
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
return Task.FromResult(0);
}
);
GraphServiceClient graphClient = new GraphServiceClient(delegateAuthenticationProvider);
var calendar = await graphClient.Me.Calendar.Request().GetAsync();
return calendar;
}
And this is how I get the access token:
public async Task<string> getAcessTokenAsync()
{
if(User.Identity.IsAuthenticated)
{
var userId = User.FindFirst("MicrosoftUserId")?.Value;
ConfidentialClientApplication cca =
new ConfidentialClientApplication( Configuration["MicrosoftAuth:ClientId"],
String.Format(System.Globalization.CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}{1}", "common", "/v2.0"),
Configuration["MicrosoftAuth:RedirectUri"]+ "signin-oidc",
new Microsoft.Identity.Client.ClientCredential(Configuration["MicrosoftAuth:ClientSecret"]),
new SessionTokenCache(userId,_memoryCache).GetCacheInstance(),
null);
var token = await cca.AcquireTokenForClientAsync(new string[]{"https://graph.microsoft.com/.default"});
return token.AccessToken;
}
else
throw new Exception("User is not autenticated");
}
Finally, this is how the authentication options look in the startup file.
services.AddAuthentication().AddOpenIdConnect(openIdOptions =>
{
openIdOptions.ResponseType = OpenIdConnectResponseType.CodeIdToken;
openIdOptions.Authority = String.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}{1}", "common", "/v2.0");
openIdOptions.ClientId = Configuration["MicrosoftAuth:ClientId"];
openIdOptions.ClientSecret = Configuration["MicrosoftAuth:ClientSecret"];
openIdOptions.SaveTokens = true;
openIdOptions.TokenValidationParameters = new TokenValidationParameters{
ValidateIssuer = false
};
var scopes = Configuration["MicrosoftAuth:Scopes"].Split(' ');
foreach (string scope in scopes){
openIdOptions.Scope.Add(scope);
}
openIdOptions.Events = new OpenIdConnectEvents{
OnAuthorizationCodeReceived = async (context) =>
{
var userId = context.Principal.Claims.First(item => item.Type == ObjectIdentifierType).Value;
IMemoryCache memoryCache = context.HttpContext.RequestServices.GetRequiredService<IMemoryCache>();
ConfidentialClientApplication cca =
new ConfidentialClientApplication( Configuration["MicrosoftAuth:ClientId"],
String.Format(CultureInfo.InvariantCulture, "https://login.microsoftonline.com/{0}{1}{2}", "common", "/v2.0", "/adminconsent"),
Configuration["MicrosoftAuth:RedirectUri"]+ "signin-oidc",
new Microsoft.Identity.Client.ClientCredential(Configuration["MicrosoftAuth:ClientSecret"]),
new SessionTokenCache(userId,memoryCache).GetCacheInstance(),
null);
var code = context.ProtocolMessage.Code;
var result = await cca.AcquireTokenByAuthorizationCodeAsync(code,new string[]{"User.Read.All", "Calendars.ReadWrite"});
context.HandleCodeRedemption(result.AccessToken, result.IdToken);
},
};
});
My app is registered in the Microsoft Application Registration Portal, and I do get a token when I request for it, so I'm not sure what could possibly be causing the problem.
Same issue with the previews thread. There are two kinds of token issued by Azure AD, delegate for use or app. The token you were acquire is using the client credentials flow which is delegate for app. There is no me context when you request using this kind of token(refer Get access on behalf of a user and Get access without a user for the difference).
To integrate Microsoft Graph with web app and delegate the user to call the Microsoft Graph, you need to use the code grant flow(OnAuthorizationCodeReceived event) as you config in the startup.cs file.
I created a new project using Visual Studio 2015 and enabled authentication using work and school accounts against Azure Active Directory.
Here is what the generated configure function looks like:
app.UseStaticFiles();
app.UseCookieAuthentication();
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = Configuration["Authentication:AzureAd:ClientId"],
ClientSecret = Configuration["Authentication:AzureAd:ClientSecret"],
Authority = Configuration["Authentication:AzureAd:AADInstance"] + Configuration["Authentication:AzureAd:TenantId"],
CallbackPath = Configuration["Authentication:AzureAd:CallbackPath"],
ResponseType = OpenIdConnectResponseType.CodeIdToken
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
Here is the rudimentary action code trying to get user groups:
public async Task<IActionResult> Index()
{
var client = new HttpClient();
var uri = "https://graph.windows.net/myorganization/users/{user_id}/$links/memberOf?api-version=1.6";
var response = await client.GetAsync(uri);
if (response.Content != null)
{
ViewData["response"] = await response.Content.ReadAsStringAsync();
}
return View();
}
What do I need to use or change this code to make sure I can get user groups?
Currently, the response is:
{
"odata.error":{
"code":"Authentication_MissingOrMalformed",
"message":{
"lang":"en",
"value":"Access Token missing or malformed."
},
"values":null
}
}
I spent the last 2 days trying to figure this out and finally got it. Azure AD is a moving target and with ASPNETCORE still maturing most documentation on how to access the Azure AD Graph is outdated. So as of right now this is how you would go about access the Azure AD Graph.
Take note of your app's clientid
Register your app with Azure Active Directory
Generate a Key in that registration and take note of it (you can only view it right after it's created)
Take note of your 'Tenant Name' (you can also use the Tenant ID)
Then you will use the above info to generate a Access Token, then use that token to make calls to the Graph.
public async void GetUsers()
{
// Get OAuth token using client credentials
string tenantName = "your-tenant-name.onmicrosoft.com";
string authString = "https://login.microsoftonline.com/" + tenantName;
AuthenticationContext authenticationContext = new AuthenticationContext(authString, false);
// Config for OAuth client credentials
string clientId = "your-client-id";
string key = "your-AzureAD-App-Key";
ClientCredential clientCred = new ClientCredential(clientId, key);
string resource = "https://graph.windows.net";
AuthenticationResult authenticationResult;
try
{
authenticationResult = await authenticationContext.AcquireTokenAsync(resource, clientCred);
}
catch(Exception ex)
{
throw new Exception(ex.Message, ex.InnerException);
}
var client = new HttpClient();
var request = new HttpRequestMessage(System.Net.Http.HttpMethod.Get, "https://graph.windows.net/your-tenant-name.onmicrosoft.com/users?api-version=1.6");
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
var response = await client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
}
One other huge gotcha that you may find that I ran into and several forums are discussing is if you get a Authorization_Request_Denied error or Insufficient_Permissions error. This is resolved by running a PowerShell command to give the application you registered with Azure AD "Administrator" permissions.
Requests to MS Graph API gives me "Authorization Request Denied - Insufficient privileges to complete the operation"
The powershell command you want to run is
Connect-MsolService
$ClientIdWebApp = '{your_AD_application_client_id}'
$webApp = Get-MsolServicePrincipal –AppPrincipalId $ClientIdWebApp
#use Add-MsolRoleMember to add it to "Company Administrator" role).
Add-MsolRoleMember -RoleName "Company Administrator" -RoleMemberType ServicePrincipal -RoleMemberObjectId $webApp.ObjectId
Hopefully this helps. Let me know if you think any refining needs to be made.
Code is much simpler with Graph client
var serviceRoot = new Uri(#"https://graph.windows.net/"+ tenantID);
var activeDirectoryClient = new ActiveDirectoryClient(serviceRoot,
() => Task.FromResult(authenticationResult.AccessToken));
// Fetch more user details from the Graph
var user = await activeDirectoryClient.Users.GetByObjectId(userObjectID).ExecuteAsync();
// fetch all groups (DG + SG) and roles transitively for the user
var userGroups = await user.GetMemberObjectsAsync(securityEnabledOnly: false);