A recent update appeared in the link below to automatically refresh tokens for Storage Accounts:
https://learn.microsoft.com/en-us/azure/storage/common/storage-auth-aad-msi
I've modified the code above and tested it with Azure Data Lake Store Gen1 successfully to auto refresh MSI tokens.
To implement the code for ADLS Gen1 I needed two libraries:
<PackageReference Include="Microsoft.Azure.Services.AppAuthentication" Version="1.2.0-preview3" />
<PackageReference Include="Microsoft.Azure.Storage.Common" Version="10.0.3" />
I then used this code to create an AdlsClient instance with a constantly refreshed token:
var miAuthentication = new AzureManagedIdentityAuthentication("https://datalake.azure.net/");
var tokenCredential = miAuthentication.GetAccessToken();
ServiceClientCredentials serviceClientCredential = new TokenCredentials(tokenCredential.Token);
var dataLakeClient = AdlsClient.CreateClient(clientAccountPath, serviceClientCredential);
Below is the class that I modified from the article to generically refresh tokens. This can now be used for auto refreshing MSI tokens for both ADLS Gen1("https://datalake.azure.net/") and Storage Accounts("https://storage.azure.com/") by providing the relevant resource address when instantiating AzureManagedIdentityAuthentication
. Make sure to use the code in the link to create the StorageCredentials
object for storage accounts.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Azure.Storage.Auth;
namespace SharedCode.Authentication
{
/// <summary>
/// Class AzureManagedIdentityAuthentication.
/// </summary>
public class AzureManagedIdentityAuthentication
{
private string _resource = null;
/// <summary>
/// Initializes a new instance of the <see cref="AzureManagedIdentityAuthentication"/> class.
/// </summary>
/// <param name="resource">The resource.</param>
public AzureManagedIdentityAuthentication(string resource)
{
_resource = resource;
}
/// <summary>
/// Gets the access token.
/// </summary>
/// <returns>TokenCredential.</returns>
public TokenCredential GetAccessToken()
{
// Get the initial access token and the interval at which to refresh it.
AzureServiceTokenProvider azureServiceTokenProvider = new AzureServiceTokenProvider();
var tokenAndFrequency = TokenRenewerAsync(azureServiceTokenProvider, CancellationToken.None).GetAwaiter().GetResult();
// Create credentials using the initial token, and connect the callback function
// to renew the token just before it expires
TokenCredential tokenCredential = new TokenCredential(tokenAndFrequency.Token,
TokenRenewerAsync,
azureServiceTokenProvider,
tokenAndFrequency.Frequency.Value);
return tokenCredential;
}
/// <summary>
/// Renew the token
/// </summary>
/// <param name="state">The state.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.Threading.Tasks.Task<Microsoft.Azure.Storage.Auth.NewTokenAndFrequency>.</returns>
private async Task<NewTokenAndFrequency> TokenRenewerAsync(Object state, CancellationToken cancellationToken)
{
// Use the same token provider to request a new token.
var authResult = await ((AzureServiceTokenProvider)state).GetAuthenticationResultAsync(_resource);
// Renew the token 5 minutes before it expires.
var next = (authResult.ExpiresOn - DateTimeOffset.UtcNow) - TimeSpan.FromMinutes(5);
if (next.Ticks < 0)
{
next = default(TimeSpan);
}
// Return the new token and the next refresh time.
return new NewTokenAndFrequency(authResult.AccessToken, next);
}
}
}