Adding ASP.NET Core authorization for an Azure Blob Storage and Azure AD users using role assignments

This post shows how authorization can be implemented for Azure Storage Blob containers in an ASP.NET Core web application. The two roles Storage Blob Data Contributor and Storage Blob Data Reader are used to authorize the Azure AD users which use the Blob storage container. Users are assigned the roles using role assignment. This authorization information is required in the ASP.NET Core application so that the users can be authorized to upload files, or just get authorized to download the files. The Azure Management Fluent rest API is used to select this data.

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

Blogs in this series

Setup the Azure App registration

To list the role assignments for Azure and Azure Storage in ASP.NET Core, a new Azure App registration was created. The service principle ID of the enterprise application (from this Azure App registration) is used to assign the contributor role on the subscription so that the application can list the role assignments from the scopes within the subscription. A client secret is setup for the application.

Setup the Role assignment for the Azure App registration

In the subscription, the Access control (IAM) blade is selected and a role assignment was added for the Azure Enterprise application which was created when we created the Azure App registration for this subscription. If the application registration is only required for the specific storage blob container, less rights would be required.

Implement the Azure Management Fluent Service

The Microsoft.Azure.Management.Fluent nuget package is used to implement the Azure rest API to access the role assignments for the Azure Storage Blob container.

The AuthenticateClient method is used to authenticate the Microsoft.Azure.Management.Fluent client using the Azure App registration clientId and the client secret from the App registration. The Authenticate method uses the Azure credentials to access the API.

private void AuthenticateClient()
{
	// clint credentials flow with secret
	var clientId = _configuration
		.GetValue<string>("AzureManagementFluent:ClientId");
	var clientSecret = _configuration
		.GetValue<string>("AzureManagementFluent:ClientSecret");
	var tenantId = _configuration
		.GetValue<string>("AzureManagementFluent:TenantId");

	AzureCredentialsFactory azureCredentialsFactory 
		= new AzureCredentialsFactory();
		
	var credentials = azureCredentialsFactory
		.FromServicePrincipal(clientId,
			clientSecret,
			tenantId,
			AzureEnvironment.AzureGlobalCloud);

	// authenticate to Azure AD
	_authenticatedClient = Microsoft.Azure.Management
		.Fluent.Azure.Configure()
		.Authenticate(credentials);
}

The GetStorageBlobDataContributors method lists all the role assignments for the scope parameter. The scope parameter is the path to the Azure storage or whatever resource you want to check. This would need to be changed for your application. The Id for the role Storage Blob Data Contributor is used to filter only this role assignment in the defined scope.

/// <summary>
/// returns IRoleAssignment for Storage Blob Data Contributor 
/// </summary>
/// <param name="scope">Scope of the Azure storage</param>
/// <returns>IEnumerable of the IRoleAssignment</returns>
private IEnumerable<IRoleAssignment> GetStorageBlobDataContributors(string scope)
{
	var roleAssignments = _authenticatedClient
		.RoleAssignments
		.ListByScope(scope);

	// https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
	// Storage Blob Data Contributor == "ba92f5b4-2d11-453d-a403-e96b0029c9fe"
	// Storage Blob Data Reader == "2a2b9908-6ea1-4ae2-8e65-a410df84e7d1"

	var storageBlobDataContributors = roleAssignments
		.Where(d => d.RoleDefinitionId
			.Contains("ba92f5b4-2d11-453d-a403-e96b0029c9fe"));

	return storageBlobDataContributors;
}

The HasRoleStorageBlobDataContributorForScope method is used to check if the user principal id of the authenticated user in the ASP.NET core application has an assigned role for the Azure Storage, which was defined using the scope.

public bool HasRoleStorageBlobDataContributorForScope(
	string userPrincipalId, string scope)
{
	var roleAssignments = GetStorageBlobDataContributors(scope);
	return roleAssignments
		.Count(t => t.PrincipalId == userPrincipalId) > 0;
}

public bool HasRoleStorageBlobDataReaderForScope(
	string userPrincipalId, string scope)
{
	var roleAssignments = GetStorageBlobDataReaders(scope);
	return roleAssignments
		.Count(t => t.PrincipalId == userPrincipalId) > 0;
}

Add authorization to the ASP.NET Core Web application

Now that the authorization data can be requested from Azure, this can be used in the ASP.NET Core application. Requirements, policies and handlers implementations can be used for this. The StorageBlobDataContributorRoleRequirement class was created using the IAuthorizationRequirement.

namespace AspNetCoreAzureStorage
{
    public class StorageBlobDataContributorRoleRequirement 
      : IAuthorizationRequirement { }
}

The StorageBlobDataContributorRoleHandler implements the AuthorizationHandler which uses the StorageBlobDataContributorRoleRequirement requirement. The handler uses the AzureManagementFluentService to query the Azure data and the handler will succeed if the user has the built in Azure role Storage Blob Data Contributor.

using Microsoft.AspNetCore.Authorization;
using System;
using System.Linq;
using System.Threading.Tasks;

namespace AspNetCoreAzureStorage
{
    public class StorageBlobDataContributorRoleHandler 
		: AuthorizationHandler<StorageBlobDataContributorRoleRequirement>
    {
        private readonly AzureManagementFluentService _azureManagementFluentService;

        public StorageBlobDataContributorRoleHandler(
			AzureManagementFluentService azureManagementFluentService)
        {
            _azureManagementFluentService = azureManagementFluentService;
        }

        protected override Task HandleRequirementAsync(
            AuthorizationHandlerContext context,
            StorageBlobDataContributorRoleRequirement requirement
        )
        {
            if (context == null)
                throw new ArgumentNullException(nameof(context));
            if (requirement == null)
                throw new ArgumentNullException(nameof(requirement));

            var scope = "subscriptions/..../storageAccounts/azureadfiles";

            var spIdUserClaim = context.User.Claims.FirstOrDefault(t => t.Type 
				== "http://schemas.microsoft.com/identity/claims/objectidentifier");

            if (spIdUserClaim != null)
            {
                var success = _azureManagementFluentService
					.HasRoleStorageBlobDataContributorForScope(spIdUserClaim.Value, scope);
                if (success)
                {
                    context.Succeed(requirement);
                }
            }

            return Task.CompletedTask;
        }
    }
}

The Startup class adds the handlers and the services to the IoC of ASPNET Core. Two policies are added, one to check the Storage Blob Data Contributor requirement and a policy for the the Storage Blob Data Reader role check.

services.AddSingleton<IAuthorizationHandler, 
	StorageBlobDataContributorRoleHandler>();
services.AddSingleton<IAuthorizationHandler, 
	StorageBlobDataReaderRoleHandler>();

services.AddAuthorization(options =>
{
	options.AddPolicy("StorageBlobDataContributorPolicy", 
		policyIsAdminRequirement =>
	{
		policyIsAdminRequirement.Requirements
			.Add(new StorageBlobDataContributorRoleRequirement());
	});
	options.AddPolicy("StorageBlobDataReaderPolicy", 
		policyIsAdminRequirement =>
	{
		policyIsAdminRequirement.Requirements
			.Add(new StorageBlobDataReaderRoleRequirement());
	});
});

The Razor page uses the policy to show or hide the data. If the user has authenticated in the ASP.NET Core application and has the required roles in Azure, the data will be returned. The checks are also validated on Azure and not just in the ASP.NET Core application.

@page
@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService
@model AspNetCoreAzureStorage.Pages.AzStorageFilesModel
@{
    ViewData["Title"] = "Azure Storage Files";
    Layout = "~/Pages/Shared/_Layout.cshtml";
}

@if ((await AuthorizationService.AuthorizeAsync(User, "StorageBlobDataContributorPolicy")).Succeeded)
{
    <div class="card">
        ///... rest of code omitted see src
    </div>
}
else
{
    <p>User has not contributor access role for blob storage</p>
}

When the application is started, the roles are checked and the views are displayed as required.

Notes:

This is just one way to use the role assignments authorization in an ASP.NET Core application using Azure Storage Blob containers. This would become complicated if using with lots of users or different types of service principals. In a follow up post we will explore other ways of implementing authorization in ASP.NET Core for Azure roles and azure role assignments.

Links Role assignments

https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles

Using Azure Management Libraries for .NET to manage Azure AD users, groups, and RBAC Role Assignments

https://management.azure.com/subscriptions/subscriptionId/providers/Microsoft.Authorization/roleAssignments?api-version=2015-07-01

https://docs.microsoft.com/en-us/rest/api/apimanagement/apimanagementrest/azure-api-management-rest-api-authentication

https://docs.microsoft.com/en-us/rest/api/authorization/role-assignment-rest-sample

Further Links:

https://github.com/Azure-Samples/storage-dotnet-azure-ad-msal

https://winsmarts.com/access-azure-blob-storage-with-standards-based-oauth-authentication-b10d201cbd15

https://stackoverflow.com/questions/45956935/azure-ad-roles-claims-missing-in-access-token

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

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

https://blog.soft-cor.com/empowering-developer-teams-to-manage-their-own-azure-rbac-permissions-in-highly-regulated-industries/

3 comments

  1. […] Adding ASP.NET Core authorization for an Azure Blob Storage and Azure AD users using role assignment… – Damien Bowden […]

  2. […] Adding ASP.NET Core authorization for an Azure Blob Storage and Azure AD users using role assignment… (Damien Bowden) […]

  3. […] Adding ASP.NET Core Authorization for an Azure Blob Storage and Azure AD users using role assignment… – This is a nice post by Damien Bod detailing authorization across multiple technologies: ASP.NET Core, Azure Blob Storage, and Azure AD. […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

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

%d bloggers like this: