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:
- Error handling
- Token caching
- 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.