How to Authenticate With Microsoft Graph API Using Managed Service Identity

Rahul Pulikkot Nath
Rahul Pulikkot Nath

Table of Contents

In an earlier post, we saw how to enable Role-Based Access for .Net Core Web applications. We used hardcoded AD Group Id's in the application as below

"AdGroups": [
  {
    "GroupName": "Admin",
    "GroupId": "119f6fb5-a325-47f9-9889-ae6979e9e120"
  },
  {
    "GroupName": "Employee",
    "GroupId": "02618532-b2c0-4e58-a32e-e715ddf07f63"
  }
]

To avoid hardcoding the id's in the application config, we can use the Graph API to query the AD groups at runtime. The GraphServiceClient from the Microsoft.Graph NuGet package can be used to connect to the Graph API. In this post, we will see how to use the API client to retrieve the AD groups. We will see two authentication mechanisms for the Graph API - one using client credentials and also using Managed Service Identity.


Using Client Credentials

To authenticate using Client Id and secret, we need to create an AD App in the Azure portal. Add a new client secret under the 'Certificates & Secrets' tab. To access the Graph API, make sure to add permissions under the 'API permissions' tab, as shown below. I have added the required permissions to read the AD Groups.

private static async Task<GraphServiceClient> GetGraphApiClient()
{
    var clientId = "AD APP ID";
    var secret = "AD APP Secret";
    var domain = "mydomain.onmicrosoft.com";

    var credentials = new ClientCredential(clientId, secret);
    var authContext = new AuthenticationContext($"https://login.microsoftonline.com/{domain}/");
    var token = await authContext.AcquireTokenAsync("https://graph.microsoft.com/", credentials);
    var accessToken = token.AccessToken;

    var graphServiceClient = new GraphServiceClient(
        new DelegateAuthenticationProvider((requestMessage) =>
    {
        requestMessage
            .Headers
            .Authorization = new AuthenticationHeaderValue("bearer", accessToken);

        return Task.CompletedTask;
    }));

    return graphServiceClient;
}

Using Managed Service Identity

With the client credentials approach, we have to manage the AD app and the associated secrets. To avoid this, we can use Managed Service identity (MSI), and the Azure infrastructure will do this for us automatically. To use MSI, turn on Identity for the Azure Web App from the Azure Portal.

For the MSI service principal to access Microsoft Graph API, we need to assign appropriate permissions. This is not possible through the Azure Portal, and we need to use PowerShell script. As before, we only need permission to read the Azure AD Groups. '00000003-0000-0000-c000-000000000000' is known Application ID of the Microsoft Graph API. Using that, we can filter out the App Roles to read the AD Group permissions.

Connect-AzureAD
$graph = Get-AzureADServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'"
$groupReadPermission = $graph.AppRoles `
    | where Value -Like "Group.Read.All" `
    | Select-Object -First 1

# Use the Object Id as shown in the image above
$msi = Get-AzureADServicePrincipal -ObjectId <WEB APP MSI Identity>

New-AzureADServiceAppRoleAssignment `
    -Id $groupReadPermission.Id `
    -ObjectId $msi.ObjectId `
    -PrincipalId $msi.ObjectId `
    -ResourceId $graph.ObjectId

As we have seen in the previous instances with MSI (here and here) we use the AzureServiceTokenProvider to authenticate with MSI to get the token. The ClientId and secret are no longer required. The AzureServiceTokenProvider class tries to get a token using Managed Service Identity, Visual Studio, Azure CLI, and Integrated Windows Authentication. In our case, when deployed to Azure, the code uses MSI to get the token.

DefaultAzureCredential: Unifying How We Get Azure AD Token
Azure Identity library provides Azure Active Directory token authentication support across the Azure SDK
private static async Task<GraphServiceClient> GetGraphApiClient()
{
    var azureServiceTokenProvider = new AzureServiceTokenProvider();
    string accessToken = await azureServiceTokenProvider
        .GetAccessTokenAsync("https://graph.microsoft.com/");

    var graphServiceClient = new GraphServiceClient(
        new DelegateAuthenticationProvider((requestMessage) =>
    {
        requestMessage
            .Headers
            .Authorization = new AuthenticationHeaderValue("bearer", accessToken);

        return Task.CompletedTask;
    }));

    return graphServiceClient;
}

Getting AD Groups Using Graph Client

With the GraphApiClient, we can use it to get the Groups from the Azure AD as below. These groups can be used to configure the Authorization policy, as shown below.

 public void ConfigureServices(IServiceCollection services)
 {
     ...
     services.AddAuthorization(options =>
     {
         var adGroups = GetAdGroups();

         foreach (var adGroup in adGroups)
             options.AddPolicy(
                 adGroup.GroupName,
                 policy =>
                     policy.AddRequirements(new IsMemberOfGroupRequirement(adGroup.GroupName, adGroup.GroupId)));
     });
     services.AddSingleton<IAuthorizationHandler, IsMemberOfGroupHandler>();
 }

 private static List<AdGroupConfig> GetAdGroups()
 {
     var client = GetGraphApiClient().Result;
     var allAdGroups = new List<AdGroupConfig>();

     var groups = client.Groups.Request().GetAsync().Result;

     while (groups.Count > 0)
     {
         allAdGroups.AddRange(
                    groups.Select(a =>
                        new AdGroupConfig() { GroupId = a.Id, GroupName = a.DisplayName }));

         if (groups.NextPageRequest != null)
             groups = groups.NextPageRequest.GetAsync().Result;
         else
             break;
     }

     return allAdGroups;
 }

The AD groups no longer need to be hardcoded in the application. Also, with Managed Service Identity, we do not need any additional AD app/credentials to be managed as part of the application.

Hope it helps!

AzureDotnet-Core