How to authenticate in Azure Blob Storage through Integrated Windows Authentication?

Viewed 112

I need to silently authenticate in Azure Blob Storage from a .NET application running on a Windows machine that is domain-joined and the domain is synced to Azure AD.

I am using this example of authentication flow as the base and trying to adapt it for Blob Storage. I successfully obtain a token from AcquireTokenByIntegratedWindowsAuth() method of PublicClientApplication, but I cannot figure out how to supply it to BlobContainerClient. The most appropriate constructor seems to be the one accepting TokenCredential, but I cannot find a suitable class among descendants of TokenCredential.

I ended up writing my own implementation of TokenCredential:

internal class IwaCredential : TokenCredential
{
    private readonly IPublicClientApplication _application;
    private readonly string[] _scopes;

    public IwaCredential(IPublicClientApplication app, string[] scopes)
    {
        _application = app;
        _scopes = scopes;
    }

    private async Task<AuthenticationResult> AuthenticateAsync()
    {
        AuthenticationResult? result = null;
        var accounts = await _application.GetAccountsAsync();

        if (accounts.Any())
        {
            try
            {
                result = await _application.AcquireTokenSilent(_scopes, accounts.FirstOrDefault()).ExecuteAsync();
            }
            catch (MsalUiRequiredException)
            {
            }
        }

        if (result == null)
        {
            result = await _application.AcquireTokenByIntegratedWindowsAuth(_scopes).ExecuteAsync();
        }

        return result;
    }

    private async Task<AccessToken> GetAccessTokenAsync()
    {
        var authResult = await AuthenticateAsync();
        return new AccessToken(authResult.AccessToken, authResult.ExpiresOn);
    }

    public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
    {
        return GetAccessTokenAsync().GetAwaiter().GetResult();
    }

    public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
    {
        return new ValueTask<AccessToken>(GetAccessTokenAsync());
    }
}

Then I am able to pass instance of that to the client:

var appOptions = new PublicClientApplicationOptions
{
    ClientId = "...",
    TenantId = "...",
};
var app = PublicClientApplicationBuilder.CreateWithApplicationOptions(appOptions).Build();
var cred = new IwaCredential(app, new string[] { "https://storage.azure.com/user_impersonation" });
var client = new BlobContainerClient(new Uri("https://foobar.blob.core.windows.net/upload"), cred);

using (Stream file = new FileStream(@"C:\Windows\win.ini", FileMode.Open, FileAccess.Read))
{
    var res = await client.UploadBlobAsync("prefix/win.ini", file);
    Console.WriteLine(res);
}

It works, but I still feel like I am missing something as I believe there should be support for that flow within the standard library.

Am I doing it right way? Please suggest improvements.

0 Answers
Related