I am trying to use GraphAPI v5 in a Blazor .NET 8.0 application, to request a list of all users who have a certain AppRole assigned.
I configure the roles in Entra Admin Centre > Enterprise applications > (my app) > Users and Groups, and assign users to Roles that I defined in the application registration.
I have tried the following code in a Razor page:
@inject GraphServiceClient GSC
@inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
// ...
protected async Task AzTest()
{
try
{
var users = await GSC.Users.GetAsync((requestConfiguration) =>
{
requestConfiguration.QueryParameters.Expand = new string[] { "appRoleAssignments" };
});
}
catch(Exception ex)
{
ConsentHandler.HandleException(ex);
}
}
I also found (thanks to commentor) that Directory.Read.All
was a required Scope for this call to work.
My question is: how do I filter the result to find just the users with a specific Role? My organization has over 1000 users.
I have tried the following approaches:
Approach 1 - Filter
Guid roleIdGuid = new Guid("5f61fba7-ae07-4438-9f41-4c00b2540463");
// ...
requestConfiguration.QueryParameters.Filter = $"appRoleAssignments/any(ar: ar/appRoleId eq {roleIdGuid})";
(this was suggested by Copilot). This gives a runtime error :
Microsoft.Graph.Models.ODataErrors.ODataError: Property 'appRoleId' does not exist as a declared property or extension property.
which Copilot suggests means that the API does not actually support filtering by App Role ID.
Approach 2 - Pagination
Another approach is to retrieve all users via Pagination, and then go through them in C# to check for the role ID. However, the pagination doesn't seem to work. Using the exact code from the V5 documentation:
var usersResponse = await graphServiceClient
.Users
.GetAsync(requestConfiguration => {
requestConfiguration.QueryParameters.Expand = new string[] { "appRoleAssignments" };
requestConfiguration.QueryParameters.Top = 1;
});
var userList = new List<User>();
var pageIterator = PageIterator<User,UserCollectionResponse>.CreatePageIterator(graphServiceClient,usersResponse, (user) => { userList.Add(user); return true; });
await pageIterator.IterateAsync();
// (omitted the later filtering code for brevity)
This ran but I waited for over 90 seconds and it was still making API calls and responses on the await
part; I'm not sure if it was stuck in an infinite loop somehow or just taking forever, but at any rate, that sort of delay isn't an acceptable solution.
appRoleId
doesn't support filter from the API document. As you can see that principalDisplayName
supports filter. So that I agree with you that we need to get all the users and then get the users assigned specific roles by ourselves.
About the Graph API Pagination, we could set page size to 999 to minimize the number of requests and I'm afraid top 1 is the reason why you waited for so long.
Then our codes shall be similar to
var usersResponse = await graphServiceClient.Users.GetAsync(requestConfiguration => {
requestConfiguration.QueryParameters.Expand = new string[] { "appRoleAssignments" };
requestConfiguration.QueryParameters.Top = 999;
});
var userList = new List<User>();
var pageIterator = PageIterator<User,UserCollectionResponse>.CreatePageIterator(graphServiceClient,usersResponse, (user) => { userList.Add(user); return true; });
await pageIterator.IterateAsync();
List<User> result = new List<User>();
foreach (var tempUser in userList) {
var roles = tempUser.AppRoleAssignments;
foreach(var role in roles) {
if (role.AppRoleId.ToString() == "role_id_here")
{
result.Add(tempUser);
break;
}
}
}