# Azure Services Authentication through Microsoft Managed Identity

One should generally avoid using Client Secrets to authenticate Azure resources via Service Principals as it can lead to inherent security risks and operational overhead. In one of my earlier articles I wrote about a workaround of overriding `ClientSecretCredential` authentication class and creating a JWT token for MSAL flow. These approaches align better with zero-trust principles, minimize risk and reduce operational overhead.

In past I had penned an article on how to leverage Managed Identity for Azure resources. You can find that article [here](https://www.azureguru.net/managed-identities-in-microsoft-azure).

But, consider a scenario where an Azure Function returns a list of containers or files from Azure Data Lake Storage Gen2 (ADLS Gen2). The Azure function serves as a backend API and a frontend application is running outside of Azure, such as in a public environment and needs to consume that list. In such a case it's important to explore alternatives for authenticating the Azure resources such as using managed identities , Azure AD token acquisition via delegated permissions or leveraging Azure API Management. Such approaches can help eliminate the need for long-lived secrets being stored and maintained.

You might argue that we can use Azure Function keys for Azure functions. But, similar to Client secrets there are inherent security risks with Azure Function keys as well.

### Can we impersonate a Managed Identity?

First and foremost, its not possible to impersonate a Managed Identity through Azure issued tokens for Service Principals. Managed Identities are a type of service principal managed by Azure and their tokens are issued by Azure AD. These tokens are bound to the identity and can’t be easily used to impersonate another identity in our case through a service principal.

So we will have to work around this limitation and that can be done through an intermediary Azure-hosted service such as Azure function that can access the Azure resources through Managed Identity on its behalf and any application/service running outside of Azure can then access the Azure function.

So the flow would be as following

* An external app gets a valid Azure AD token scoped to the Azure Function
    
* The Azure Function is secured with Azure AD and validates that token correctly.
    
* The Function’s managed identity is used to call another Azure service
    
* The Azure Function validates a token generated by a Service Principal and accepts it if the token is intended for the Function’s API.
    

Lets see how.

### SetUp

First, we will create an Azure function that access ADLS Gen2 through Managed Identity.

I will be using an existing User Managed Identity and an existing ADLS Gen2 storage in my Azure tenant.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760712997667/97e342f9-e2bb-454b-a593-8dcb504455c7.png align="center")

In the next step, assign a reader role or higher to the managed identity to access the Azure storage

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760727102695/81959d48-d510-4f1a-a241-369dcd3a920d.png align="center")

Once done, we design our Azure Function.

Few points to be noted for the Azure function

* We will use `DefaultAzureCredential` to authenticate through Managed Identity
    
* We will use the `DataLakeServiceClient` to fetch the details from the ADLS Gen2 storage
    
* The final output will be a JSON string that contains a list of directories in a give container
    

**Note :** The primary goal of the article is to demonstrate the extended applications of Managed Identities rather than delve deeply into the process of retrieving details for ADLS2.

For in-depth details of that topic please refer to my previous article

[https://www.azureguru.net/using-azure-data-lake-service-to-manage-fabric-lakehouse](https://www.azureguru.net/using-azure-data-lake-service-to-manage-fabric-lakehouse)

### Code

Create a new C# Azure function of the type Http trigger

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760728949742/8724e4a0-754a-40b1-8f68-c5366ea518e3.png align="center")

Add the following references to the project.

```csharp
using Azure.Identity;
using Azure.Storage.Files.DataLake;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
using System.Text.Json;
```

Some of the above references are added by default when you create a new Azure function project while references for `Azure.Identity; Azure.Storage.Files.DataLake; System.Text.Json` have to be added explicitly.

In the Run method of the Azure function add the following code

```csharp
  [Function("Function1")]
  public async Task<OkObjectResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequest req)
  {
      {
          string storageAccount = "Your Storage Account";
          string fileSystemName = "The Container Name";               
          string jsonResult = "";
          string userAssignedClientId = "Managed Identity Cliend Id";
          DataLakeServiceClient datalake_Service_Client;
          DataLakeFileSystemClient dataLake_FileSystem_Client;
          try
          {

              string dfsUri = $"https://{storageAccount}.dfs.core.windows.net";

              var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
              {
                  ManagedIdentityClientId = userAssignedClientId
              });

              datalake_Service_Client = new DataLakeServiceClient(new Uri(dfsUri), credential);
              dataLake_FileSystem_Client = datalake_Service_Client.GetFileSystemClient(fileSystemName);
              var fileList = new List<string>();
              foreach (var pathItem in dataLake_FileSystem_Client.GetPaths(""))
              {
                  fileList.Add(pathItem.Name);
              }

              jsonResult = JsonSerializer.Serialize(fileList);
          }
          catch (Exception ex)
          {
              _logger.LogError(ex, "Error accessing ADLS Gen2");

          }

          return new OkObjectResult(jsonResult.ToString());
      }
```

We have used the `DatalakeServiceClient` and `DatalakeFileSystemClient` to traverse through the ADLS2 storage account and fetch the list of blobs(subdirectories) of a given container. The code can be further extended to traverse recursively across each of those blobs and get the details from every child blobs and files in them.

But for this article we will keep the code constrained to return only the names of the first level directories.

So in my case the Azure function through the above code should return Customers and Recipe.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760729696067/2f31df82-06dc-498b-8d22-1e11fe14bb83.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760730318352/753ec8cd-2dd4-405e-b949-3eb52fe665de.gif align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760730251125/8bf831fa-bc33-4ccc-9957-332623088b56.png align="center")

Now that Azure Function is working as expected, we go ahead and deploy it.

You could use any of the function app hosting plan. I chose the App Service plan

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760858975754/11af3fc2-c7c9-4e79-9aed-bd459b7a13ef.png align="center")

Once successfully deployed , ensure that the Function App is up and running

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760859893168/ce6636c9-4eff-4a4b-8e92-46e9697c0e4b.png align="center")

Now comes the most important and crucial part.

How to make an external app or service(running out of Azure) to consume methods exposed through the deployed Azure functions that leverages Managed Identity. We also have to ensure that we don’t want to use client secrets.

There are few things that we need to be aware of.

The audience (aud) in the token should correspond to the Azure Function’s App Registration (the API) and that audience is configured as allowed in the Azure Function’s authentication settings.

The Service Principal requests a token for that same audience (i.e., the Azure Function’s API), then the Azure Function will accept and validate the token successfully.

![Diagram showing an external token exchanged for an access token and accessing Azure](https://learn.microsoft.com/fi-fi/entra/workload-id/media/workload-identity-federation/workflow.svg align="left")

*Image Cred : Microsoft*

**Please note the image above describes the flow for federated credentials. But it can work for our scenario as well.**

For this, the very first step is to create a Service Principal or use an existing one.

I have named my service principal as **“Azure Function Managed identity”.** Once the service principal is created then in the Authentication option add the following URL in the service principal’s Redirect URIs option.

https://<mark>{Azure Function URI}</mark>/.auth/login/aad/callback

In my case it is the following marked below in red

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760860728041/8349a2a2-6a75-42b9-947b-768f9fbfba2f.png align="center")

The next most important step is to set the Application ID URI and add the required API scopes for access control.

Under the Expose an API option, click the Add button and enter the application ID URI.

The format should be api://<mark>{ApplicationId}</mark>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760861327855/8b0c4624-5e19-4de3-a5a0-0b540ac69622.jpeg align="center")

The application ID URI is the client ID of the Service Principal. You can get that from the client ID option of the service principal as seen from the following screenshot.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760861529885/ab6189ed-704b-41c5-8b01-1d5b9c1a9ed6.png align="left")

In the next step define a new scope. Select **“Expose and API”** option and insert the `user_impersonation` scope as seen below.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760903430761/ca57f204-cfa0-46f2-a995-c34f1ef1bf5d.png align="center")

Once added the scope should be visible.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760900460343/d18b1028-5fdb-48da-bce7-e77034c9a0f4.png align="center")

Next under “API permissions”, Click on **Add a permission** and select the registered app we created in the previous step.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760902742033/20abfe46-02fb-46c1-84ed-98d85a11de7f.png align="center")

Now the Service Principal should have the necessary API and scope permissions.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760893079456/bd8259d3-a6b4-4239-993f-9bc052c50987.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">This might sound counterintuitive to see that we have to assign permissions for an API to the service principal that created the API. But that’s the way it is. Not doing so results is Unauthorized access error.</div>
</div>

In the next step, we have to set App Service Authentication and the Identity Provider for the Azure Function that we created.

Before we do that, lets see as to why it is required.

This is the Console app code that invokes the Azure function

```csharp
       var app = PublicClientApplicationBuilder
        .Create(clientId)
        .WithAuthority($"https://login.microsoftonline.com/{tenantId}")
        .WithRedirectUri("http://localhost") 
        .Build();

       string[] scopes = new[] { $"api://{clientId}/.default" };

       var result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();

       var httpClient = new HttpClient();
       httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "test");
       var response = await httpClient.GetAsync("https://azurefunctionmanagedidentities.azurewebsites.net/api/Function1");
       string str = await response.Content.ReadAsStringAsync();
```

Check out this line

```csharp
  httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "test");
```

I have set the bearer token to a fake value “test”.

With missing authentication settings for the function app, there is no mechanism to validate the incoming authentication token.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760894745542/c8164a23-df34-4ca0-94e6-f9360a57558a.png align="center")

So by default any incoming token value is treated as a valid and results are returned.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760897583650/1bf3edee-0183-425f-b64c-716c7788c6df.png align="center")

Lets now set Authentication without adding identity provider.

We will follow the process mentioned [here](https://learn.microsoft.com/fi-fi/entra/workload-id/workload-identity-federation#how-it-works).

Though the link talks about Federated identity(a topic for another blog) we can use the same steps in our case as well.

As its not possible to only set authentication without identity provider ,we will first add the identity provider and then delete it.

We select Microsoft as our Identity Provider by clicking **“Add identity provider”** in the Authentication tab

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760895358339/4ba8c606-bfe6-4a96-8222-85d992ca9a23.png align="left")

Since there is an existing Service Principal, we will use that service principal else we can create a new one under the **“Create new app registration”** option.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760895462806/80e797ce-3114-438d-a679-c733a17f31e8.jpeg align="left")

The **“Allowed token audiences”** will be the application URI that we created earlier for the service principal seen in the screenshot below.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760895571103/c8be8d97-72ec-4a16-bca0-376a2b78dcd9.png align="center")

Keep all the rest of settings to default. The only change would be to the Unauthorized redirection to 401

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760895779498/37e03e1a-40f4-49a8-ba29-f18271a48d96.png align="center")

Now that we have Authentication and the Identity Provider set, we will delete the Identity provider

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760896553719/5c0123c8-bc5e-42b9-a851-1df611574bda.png align="center")

Post delete, we get a warning sign to add an Identity Provider or Remove the authentication.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760896608352/a85330e9-34f7-486a-b269-0a1c6b78006c.png align="center")

Without deleting the Authentication and without an Identity provider, execute the console application ,we get unauthorized access error though we are passing a correct access token.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760897350926/99b0d5ea-c9f0-4e57-be4b-c8336a5ad1b0.png align="center")

This happens because we don’t have an identity provider that would validate the token and the scope and any incoming token is invalidated.

So revert back and redo all the steps to create the Identity provider

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760897187361/20a38bda-04b6-4e73-bc1d-568bca54e880.jpeg align="center")

Now re execute the console application and the code returns a list of sub directories for a given ADLS2 container.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1760897300225/efaa3ac3-dfbd-4fa5-965c-d77dbb5fb6b5.png align="center")

Now we are all set. Go ahead and execute the console application code.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1761054291456/bb13f72d-7af8-4b42-bb7c-1c55f8f9d540.gif align="center")

The screengrab above shows that initially, there are only two directories in the container and the console application displays those same two directories returned through the Azure function.

After adding a third container the console application displays the existing two directories and the newly added third directory.

### Conclusion

In conclusion, while direct impersonation of a Managed Identity via a service principal is not supported, using a trusted Azure-hosted intermediary such as Azure function provides a secure alternative for enabling access to protected resources.

Thanks for reading !!!
