Azure Function To Azure Function Request using DefaultAzureCredential and HttpClient
Asked Answered
S

1

6

I need to call a Http Azure Function from another Azure Function.

At present, I call an Azure Key Vault to get the target Function's Key, and put that in the URL as documented here: https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook-trigger?tabs=csharp#api-key-authorization

However, I want to start using a Managed Identity and DefaultAzureCredential but I cannot find out how to use DefaultAzureCredential with HttpClient or similar.

How could I use DefaultAzureCredential and HttpClient to call a Function from another Function?

Sukey answered 19/2, 2022 at 17:22 Comment(0)
B
11

The simplistic way of solving this issue is like this:

var targetFunctionAppAppRegistrationApplicationId = "A Guid that you must get from your target Function's Authentication configuration - 'App (client) ID'";
var url = "https://yourfunctionappname.azurewebsites.net/api/targetfunctionname";
var creds = new DefaultAzureCredential();
var token = await creds.GetTokenAsync(new Azure.Core.TokenRequestContext(new[] { targetFunctionAppAppRegistrationApplicationId }));
using (HttpClient client = new HttpClient())
{
  client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Token);
  var result = await client.GetAsync(url);
  // Anything else you want to do with the result
}

Credits for the above to https://spblog.net/post/2021/09/28/call-azure-ad-secured-azure-function-from-logic-app-or-another-function-with-managed-identity

However

The code above will soon cause socket exhaustion. The correct way is to use HttpClientFactory, as explained here: https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests

Since this specific use case is not covered in those docs, below is an example of how it would look like.

First, you need a MessageHandler:

public class AzureDefaultCredentialsAuthorizationMessageHandler : DelegatingHandler
{
  private readonly TokenRequestContext TokenRequestContext;
  private readonly DefaultAzureCredential Credentials;

  public AzureDefaultCredentialsAuthorizationMessageHandler()
  {
    // This parameter is actually a list of scopes.
    // If your target Function has defined scopes then you should use them here.
    // TokenRequestContext also supports many other options you should probably check out.
    TokenRequestContext = new (new[] { "targetFunctionAppAppRegistrationApplicationId" }); 
    Credentials = new DefaultAzureCredential();
  }

  protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  {
    var tokenResult = await Credentials.GetTokenAsync(TokenRequestContext, cancellationToken);
    var authorizationHeader = new AuthenticationHeaderValue("Bearer", tokenResult.Token);
    request.Headers.Authorization = authorizationHeader;
    return await base.SendAsync(request, cancellationToken);
  }
}

You then need to register an HttpClient with this message handler in your Dependency Injection container. If you're using the standard IServiceCollection:

services
  .AddScoped<AzureDefaultCredentialsAuthorizationMessageHandler>()
  .AddHttpClient<YourClassUsingTheHttpClient>((serviceProvider, httpClient) => 
  {
    httpClient.BaseAddress = "https://yourfunctionappname.azurewebsites.net/api/targetfunctionname";
  }).AddHttpMessageHandler<AzureDefaultCredentialsAuthorizationMessageHandler>();

Finally, just have a YourClassUsingTheHttpClient class that takes an HttpClient in its constructor:

public class YourClassUsingTheHttpClient
{
  public YourClassUsingTheHttpClient(HttpClient httpClient) { ... }
}

Notes

It should be noted that the code above does not deal with other important concerns like:

  1. Error handling
  2. Token caching
  3. Ability to have different HttpClients and MessageHandlers for different API endpoints.

Error handler should be straightforward to add. The rest go beyond the scope of this question.

Bowman answered 24/2, 2022 at 11:1 Comment(5)
Just what I was looking for many thanks.Sukey
This is the part I have. Now to configure the serving web app (I don't have a function) to allow access only to my test user account and the client app. I haven't found this setting yet. Do I need an app registration for this?Organography
The "serving web app" will receive the tokens (JWT, issued and signed by AAD) as part of the request (Bearer header). The steps to read, validate and ultimately accept those tokens will depend on the framework you're using for your "serving web app".Bowman
I'm surprised while reading this answer that there doesn't exist a native integration with HttpClient from Microsoft themselves that already handles everything including caching. Are you aware if something like that has been created after you posted this answer?Hawaiian
No. I'm a heavy user of this scenario and usually on top of the evolution of .net and C#, and I'm not aware of any additions to cover this specific scenario.Bowman

© 2022 - 2024 — McMap. All rights reserved.