azuremicrosoft-graph-apiasp.net-core-webapi.net-8.0

How to create a working GraphServiceClient and query Graph for group names


I am writing a REST API with C#/.NET 8 and Minimal API, which is called from a Web-GUI. I get a JWTSecurityToken, and a list of group ids in the payload of the JWTSecurityToken. I need the names of the groups. Seems I have to query Graph to get these names.

In Entra admin center my application has the API permission "Group.Read.All" set, granted by an admin. And the JWTSecurityToken contains this in "scp" in the payload.

I try to create a GraphServiceClient this way (maybe there's a better way, I tried various others - this one at least yields a GraphServiceClient):

var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
// tenantId, clientId, clientSecret and scopes read from appsettings.json or secrets.json.

Then I try to get the group name:

string groupId = "<just for test hard coded one existing group id from the token>";
var singleGroup = await graphClient.Groups[groupId].GetAsync();
Console.WriteLine(singleGroup.DisplayName);

This results in different error messages, depending on the value of scopes I use in new GraphServiceClient(clientSecretCredential, scopes):

For the scopes

scopes = ["Group.Read.All"];
scopes = [$"api://{clientId}/Group.Read.All"];

I get Azure.Identity.AuthenticationFailedException: ClientSecretCredential authentication failed: AADSTS1002012: The provided value for scope is not valid. Client credential flows must have a scope value with /.default suffixed to the resource identifier(application ID URI).

For the scopes (having variations of .default)

scopes = ["Group.Read.All", "https://graph.microsoft.com/.default"];
scopes = ["Group.Read.All", "graph.microsoft.com/.default"];
scopes = ["Group.Read.All", ".default"];
scopes = ["graph.microsoft.com/.default"];

I get Azure.Identity.AuthenticationFailedException: ClientSecretCredential authentication failed: AADSTS70011: The provided request must include a 'scope' input parameter. The provided value for the input parameter 'scope' is not valid. The scope is not valid.

For the scopes

scopes = [".default"];
scopes = ["https://graph.microsoft.com/.default"];

I get Microsoft.Graph.Models.ODataErrors.ODataError: Insufficient privileges to complete the operation.

Am I using the right Graph package? Examples use Microsoft.Graph or Microsoft.Identity.Web.MicrosoftGraph, which one is correct?

Do I use a correct startup routine? I have:

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

Most examples have

.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)

and later

.AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"))

which leads to a compiler error when I try to add it to my code.

Please, can someone point me to a working example for this? All the examples I found seem to be for older versions of Graph and are not compatible with .NET 8.

Edit: API Permissions, as requested: API Permissions


Solution

  • Note that, client credentials flow won't work with permissions of Delegated type. The correct scope to use with client credentials flow is https://graph.microsoft.com/.default.

    Initially, I too got same error when I tried to retrieve group name by granting Delegated permissions with client credentials flow:

    enter image description here

    To resolve the error, add Group.Read.All permission of Application type and make sure to grant admin consent to it while working with client credentials flow like this:

    enter image description here

    When I ran the code again after granting Application type permission, I got the response successfully with group name in response as below:

    using Azure.Identity;
    using Microsoft.Graph;
    
    
    class Program
    {
        static async Task Main(string[] args)
        {
            var tenantId = configuration["AzureAd:TenantId"];
            var clientId = configuration["AzureAd:ClientId"];
            var clientSecret = configuration["AzureAd:ClientSecret"];
    
            var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
    
            var graphClient = new GraphServiceClient(clientSecretCredential, new[] { "https://graph.microsoft.com/.default" });
    
            string groupId = "groupId";
    
            try
            {
                var group = await graphClient.Groups[groupId].GetAsync();
                Console.WriteLine($"Group Display Name: {group.DisplayName}");
            }
            catch (ServiceException ex)
            {
                Console.WriteLine($"Error retrieving group info: {ex.Message}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An error occurred: {ex.Message}");
            }
        }
    }
    

    Response:

    enter image description here