I am getting the access token using the default azure credentials method while using the managed identity of the function app to get the access token.I am able to get the token. but now I am not sure how I will unit test that method. Here is the current state
private async Task RefreshTokenCache()
{
var tokenCredential = new DefaultAzureCredential();
var accessToken = await tokenCredential.GetTokenAsync(
new TokenRequestContext(scopes: new string[] { _config[AppConstants.ADAPIAppId] + "/.default" }) { });
accessTokenCache.Set(AppConstants.AccessToken, accessToken.Token, DateTimeOffset.Now.AddMinutes(55));
}
Is there anything like httpclientfactory where I can inject or I can pass some connectionstring so that I tell DefaultAzureCredential not to make the call to Azure?
update
I am adding more context. I am using this to fetch the access token in my function app from azure to authenticate itself to the APIM. so I am using a HttpMessageHandler in that I am checking If the token doesnt exist make a call to Azure and get the token.
Startup in Function App.
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddHttpClient(AppConstants.ReplyService)
//Adding token handler middleware to add authentication related details.
.AddHttpMessageHandler<AccessTokenHandler>();
}
Handler File:
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Use the token to make the call.
// other details
request.Headers.Authorization = new AuthenticationHeaderValue(AppConstants.AuthHeader, await GetToken());
return await base.SendAsync(request, cancellationToken);
}
private async Task<string> GetToken(bool needsRefresh = false)
{
if (accessTokenCache.GetCount() == 0 || needsRefresh)
{
var tokenCredential = new DefaultAzureCredential();
var accessToken = await tokenCredential.GetTokenAsync(
new TokenRequestContext(scopes: new string[] { _config["AppId"] + "/.default" }) { });
accessTokenCache.Set("accessToken", accessToken.Token, DateTimeOffset.Now.AddMinutes(55));
}
return accessTokenCache.Get("accessToken")?.ToString() ?? throw new Exception("Unable to Fetch Access Token from Cache");
}
You shouldn't be using DefaultAzureCredential
like this. It needs to be injected into service as part of the DI layer, for example here I am setting up a new BlobServiceClient
:
private static void addAzureClients(IFunctionsHostBuilder builder)
{
builder.Services.AddAzureClients(builder =>
{
try
{
builder.AddBlobServiceClient(Environment.GetEnvironmentVariable("AzureWebJobsStorage"));
builder.UseCredential(new DefaultAzureCredential());
}
catch(Exception ex)
{
throw new Exception($"Error loading AddBlobServiceClient: {Environment.GetEnvironmentVariable("AzureWebJobsStorage")}", ex);
}
});
}
I then consume the client using dependancy Injection:
public MyClass(BlobServiceClient blobServiceClient)
{
this.blobServiceClient = blobServiceClient;
}
and I never have to touch the DefaultAzureCredential
class at all. Then when unit testing I mock the BlobServiceClient
which is an abstract class.
If you really sure you want to actually use DefaultAzureCredential
then your answer is still dependency injection. I'd suggest you set it up thus:
In your startup:
builder.Services.AddSingleton<TokenCredential>(() => new DefaultAzureCredential());
Then (much like above) inject this into your class:
public MyClass(TokenCredential tokenCredential)
{
this.tokenCredential= tokenCredential;
}
Then in your test you mock TokenCredential
. You cannot mock a class that you new
. So you need to now do that