I'm trying to create REST endpoints for creating users in Azure Entra (AD) and validating if a user is present in a group or not. These endpoints will be called from custom frontend apps.
I tried the following code, but unfortunately, I'm facing the below error:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Graph;
using Microsoft.Graph.Models;
using Microsoft.Identity.Client;
using Azure.Identity;
// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace Entra_SPIKE.Controllers
{
[ApiController]
[Route("[controller]")]
public class CreateUserController : Controller
{
private GraphServiceClient _graphServiceClient;
public CreateUserController(GraphServiceClient graphServiceClient)
{
_graphServiceClient = graphServiceClient;
}
[HttpPost(Name = "PostCreateUser")]
public async Task<IActionResult> CreateUser([FromBody] CreateUserRequest request)
{
try
{
var user = new User
{
AccountEnabled = request.AccountEnabled,
DisplayName = request.DisplayName,
MailNickname = request.MailNickname,
UserPrincipalName = request.UserPrincipalName ,
PasswordProfile = new PasswordProfile
{
ForceChangePasswordNextSignIn = request.ForceChangePasswordNextSignIn,
Password = request.Password
}
};
// The client credentials flow requires that you request the
// /.default scope, and pre-configure your permissions on the
// app registration in Azure. An administrator must grant consent
// to those permissions beforehand.
var scopes = new[] { "https://graph.microsoft.com/.default" };
// Values from app registration
var tenantId = "MY-ID";
// Values from app registration
var clientId = "MY-ID";
var clientSecret = "MY-Password";
// using Azure.Identity;
var options = new ClientSecretCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud,
};
// https://learn.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);
var graphclient = new GraphServiceClient(clientSecretCredential, scopes);
var result = await graphclient.Users.PostAsync(user);
return Ok("User created successfully");
}
catch (Exception ex)
{
return StatusCode(500, $"An error occurred: {ex.Message}");
}
}
}
public class CreateUserRequest
{
public string DisplayName { get; set; }
public string MailNickname { get; set; }
public string UserPrincipalName { get; set; }
public string Password { get; set; }
public bool AccountEnabled { get; set; }
public bool ForceChangePasswordNextSignIn { get; set; }
}
}
System.InvalidOperationException: Unable to resolve service for type 'Microsoft.Graph.GraphServiceClient' while attempting to activate 'Entra_SPIKE.Controllers.CreateUserController'. at Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, Boolean isDefaultParameterRequired)
...
Could anyone please guide me on this?
First of all, Azure AD provides several kinds of authentication flows, client credential flow is used for Daemon application which doesn't have a UI to let users sign in to get authentication, Daemon application can only authenticate on behalf of the application itself. While if the application is SPA or MVC app which having UI, so that it can let user sign in, then auth code flow might be another option so that the app could help to authenticate on behalf of the user.
Coming back to your scenario, you are having an API project, and in the Controller methods you want to call Graph API via Graph SDK, so that you have to authenticate the GraphServiceClient
.
Using _graphServiceClient
via DI always use for auth code flow which usually use along with builder.Services.AddRazorPages().AddMicrosoftIdentityUI();
and builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme).AddMicrosoftIdentityWebApp(builder.Configuration).EnableTokenAcquisitionToCallDownstreamApi(initialScopes).AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi")).AddInMemoryTokenCaches();
. This requires users to sign in.
But web API can't let users sign in, so that we have another option, using On-behalf-of flow so that we can still authenticate GraphServiceClient on behalf of the user. We can use builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration).EnableTokenAcquisitionToCallDownstreamApi().AddMicrosoftGraph(builder.Configuration.GetSection("Graph")).AddInMemoryTokenCaches();
instead.
If it's not necessary for us to authenticate GraphServiceClient on behalf of the user, then doing just like what you already had var graphclient = new GraphServiceClient(clientSecretCredential, scopes);
which is for client credential flow, then the dependency inject is not required, just comment them to avoid the exception. By the way, the exception always coming from not having the builder.service.xxx.AddMicrosoftGraph
. And if you added that part but without user sign in information, it would throw another exception.
I had a test before using On-behalf-of flow in web api project, you might check it if you require. That sample used Graph SDK v4.x.