I get a JwtSecurityToken with a list of groupIds and I need the names of the groups. I already learned that I have to use MS Graph for this. But by now all I get are different kinds of errors. I've searched and tried so many examples, but I read there were many changes in these APIs, so older examples are not working any more, and I didn't find a recent working one.
So here is my small test application:
public static void Main(string[] args)
{
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddAuthorization();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
WebApplication app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
// This call is just to prove that the Authorization is okay
app.MapGet("/Hi", () => "Hi, I'm secured :-)").WithOpenApi().RequireAuthorization();
app.MapGet("/GraphCallAsync", (HttpContext context) => GetGroupInfo(context, app)).WithOpenApi().RequireAuthorization();
app.Run();
}
So I am trying to create a Graph Client, with many variations, but all of them lead to errors when I call graphClient.Groups, see the comments:
static GraphServiceClient CreateGraphClient(WebApplication app)
{
string? tenantId = app.Configuration["AzureAd:TenantId"];
string? clientId = app.Configuration["AzureAd:ClientId"];
string? clientSecret = app.Configuration["AzureAd:ClientSecret"];
var scopes = new string[];
scopes = new string[] { "Group.Read.All" };
//=> Azure.Identity.AuthenticationFailedException: ClientSecretCredential authentication failed:
// AADSTS1002012: The provided value for scope Group.Read.All is not valid.
// Client credential flows must have a scope value with /.default suffixed to the resource identifier(application ID URI).
scopes = new string[] { "https://graph.microsoft.com/.default" }
//=> Microsoft.Graph.Models.ODataErrors.ODataError: Insufficient privileges to complete the operation.
scopes = new string[] { $"https://{clientId}/.default" };
//=> Azure.Identity.AuthenticationFailedException: ClientSecretCredential authentication failed:
// AADSTS500011: The resource principal named https://<** ClientId **> was not found in the tenant named xxxxxx.
// This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant.
//You might have sent your authentication request to the wrong tenant.
scopes = new string[] { "Group.Read.All", "https://graph.microsoft.com/.default" };
//=> 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 Group.Read.All https://graph.microsoft.com/.default is not valid.
var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
return graphClient;
}
And in this function I put the call:
public static async Task<IResult> GetGroupInfo(HttpContext context, WebApplication app)
{
Microsoft.Graph.GraphServiceClient graphClient = CreateGraphClient(app);
string groupId = "<*** a valid groupId, just for test ***>";
// this call yields the error
var singleGroup = await graphClient.Groups[groupId].GetAsync();
// more code to evaluate singleGroup.DisplayName in case the call would work
// with the parameter HttpContext I can analyze the token
}
So, is the syntax of my scopes-array wrong or is there some other code or setting to change?
Sridevi postet the solution, the Group.Read.All permission has to be of Application type, see How to create a working GraphServiceClient and query Graph for group names