Delegated read and application write access to blob storage using ASP.NET Core with Entra ID authentication

This article shows how an ASP.NET Core application can control the write access to an Azure blob storage container using an application app registration. Microsoft Entra ID is used to control the user access and to implement the authentication of the web application.

Code: https://github.com/damienbod/AspNetCoreEntraIdBlobStorage

Blogs in this series

The solution provides a secure upload and secure download using Microsoft Entra ID. Users are authenticated using Microsoft Entra ID. The blob storage containers use Microsoft Entra ID security groups to control the read access. The upload access uses the an Enterprise application for the contributor role. This is used from the ASP.NET Core application for file upload.

In Azure, only the application can upload files and the users of the groups can only read the files.

The RBAC are setup as shown:

Blob storage application write access

The application can upload files to Azure blob storage. Client secrets are used to acquire the access token using the client credentials OAuth flow. This can be further improved by using a certificate and using the client assertions or even a managed identity from the host service. For development, I normally use a secret for simplicity.

using Azure.Identity;

namespace DelegatedReadAppWriteBlobStorage.FilesProvider.AzureStorageAccess;

public class ClientSecretCredentialProvider
{
    private readonly IConfiguration _configuration;

    public ClientSecretCredentialProvider(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public ClientSecretCredential GetClientSecretCredential()
    {
        // Values from app registration
        var tenantId = _configuration.GetValue<string>("AzureAd:TenantId");
        var clientId = _configuration.GetValue<string>("ApplicationClient:ClientId");
        var clientSecret = _configuration.GetValue<string>("ApplicationClient:ClientSecret");

        var options = new ClientSecretCredentialOptions
        {
            AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
        };

        // https://docs.microsoft.com/dotnet/api/azure.identity.clientsecretcredential
        var clientSecretCredential = new ClientSecretCredential(
            tenantId, clientId, clientSecret, options);

        return clientSecretCredential;
    }
}

The BlobApplicationUploadProvider uses the token and makes it possible to upload files to the Azure Blob storage. The provider uses the IFormFile as a parameter for the file data. Meta data is persisted in the local SQL database.

using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;

namespace DelegatedReadAppWriteBlobStorage.FilesProvider.AzureStorageAccess;

public class BlobApplicationUploadProvider
{
    private readonly IConfiguration _configuration;
    private readonly ClientSecretCredentialProvider _clientSecretCredentialProvider;

    public BlobApplicationUploadProvider(ClientSecretCredentialProvider clientSecretCredentialProvider,
        IConfiguration configuration)
    {
        _configuration = configuration;
        _clientSecretCredentialProvider = clientSecretCredentialProvider;
    }

    public async Task<string> AddNewFile(BlobFileUploadModel blobFileUpload, IFormFile file)
    {
        try
        {
            return await PersistFileToAzureStorage(blobFileUpload, file);
        }
        catch (Exception e)
        {
            throw new ApplicationException($"Exception {e}");
        }
    }

    private async Task<string> PersistFileToAzureStorage(
        BlobFileUploadModel blobFileUpload,
        IFormFile formFile,
        CancellationToken cancellationToken = default)
    {
        var storage = _configuration.GetValue<string>("AzureStorage:StorageAndContainerName");
        var fileFullName = $"{storage}/{blobFileUpload.Name}";
        var blobUri = new Uri(fileFullName);

        var blobUploadOptions = new BlobUploadOptions
        {
            Metadata = new Dictionary<string, string?>
            {
                { "uploadedBy", blobFileUpload.UploadedBy },
                { "description", blobFileUpload.Description }
            }
        };

        var blobClient = new BlobClient(blobUri, _clientSecretCredentialProvider.GetClientSecretCredential());

        var inputStream = formFile.OpenReadStream();
        await blobClient.UploadAsync(inputStream, blobUploadOptions, cancellationToken);

        return $"{blobFileUpload.Name} successfully saved to Azure Blob Storage Container";
    }
}

Blob storage delegated read access

The blob storage reader access is given to all users in the security group. This security group uses the app-role from the Azure app registration and the group can access the blob container, not the blob account. No SAS are used .

using Azure.Core;
using Microsoft.Identity.Client;
using Microsoft.Identity.Web;

namespace DelegatedReadAppWriteBlobStorage.FilesProvider.AzureStorageAccess;

public class DelegatedTokenAcquisitionTokenCredential : TokenCredential
{
    private readonly ITokenAcquisition _tokenAcquisition;
    private readonly IConfiguration _configuration;

    public DelegatedTokenAcquisitionTokenCredential(ITokenAcquisition tokenAcquisition,
        IConfiguration configuration)
    {
        _tokenAcquisition = tokenAcquisition;
        _configuration = configuration;
    }

    public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken)
    {
        throw new NotImplementedException();
    }

    public override async ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken)
    {
        string[]? scopes = _configuration["AzureStorage:ScopeForAccessToken"]?.Split(' ');

        if (scopes == null)
        {
            throw new Exception("AzureStorage:ScopeForAccessToken configuration missing");
        }

        AuthenticationResult result = await _tokenAcquisition
            .GetAuthenticationResultForUserAsync(scopes);

        return new AccessToken(result.AccessToken, result.ExpiresOn);
    }
}

Notes

Using an application for write access to the blob storage has both advantages and disadvantages in context of security. Removing the write access to all users is positive, but when using applications or if the application has weak security, this approach can be bad. Using a managed identity for the deployed service would improve the security and would remove the need for secrets or certificates to authenticate the application. This prepares the access for the next solution which would be to implement a multi-tenant or multi-client application using the Azure blob.

Links

Using Blob storage from ASP.NET Core with Entra ID authentication

https://learn.microsoft.com/en-us/azure/storage/blobs/authorize-access-azure-active-directory

https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction

https://github.com/AzureAD/microsoft-identity-web

5 comments

  1. […] Delegated read and application write access to blob storage using ASP.NET Core with Entra ID authent… […]

  2. I am following along with the asp.net samples, thank you, they are very helpful!

    Do you have a sample bicep/ARM to get the application setup correctly in EntraID?

    I am constantly getting ‘not authenticated’ as a response to signing in using known good credentials un/pwd.

    I am also attempting to use the WPF/WinForms desktop implementation of the Microsoft EntraID auth implementation, but with the same result.

  3. […] Delegated read and application write access to blob storage using ASP.NET Core with Entra ID authent… (Damien Bowden) […]

  4. […] Delegated read and application write access to blob storage using ASP.NET Core with Entra ID authent… – Damien Bowden […]

  5. […] Delegated read and application write access to blob storage using ASP.NET Core with Entra ID authent… […]

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.