Search code examples
c#.netazureazure-storageazure-managed-identity

Azure Blob Storage Authorization Failure – Tried Managed Identity & SAS Token Without Success


Here's a detailed and well-structured StackOverflow post for you to use:


Azure Blob Storage Authorization Failure – Tried Managed Identity & SAS Token Without Success

We are working on an Azure-based web application and are encountering persistent authorization issues when attempting to upload files to Azure Blob Storage. Despite trying multiple authentication methods (Managed Identity & SAS tokens), we continue to receive AuthorizationFailure errors.

🛠️ Our Architecture

  • Frontend: React (Hosted on Azure Static Web Apps)
  • Backend: .NET Core API (Hosted on Azure App Service)
  • Database: PostgreSQL
  • Storage: Azure Blob Storage
  • Authentication Attempted:
    • Managed Identity Authentication (Preferred)
    • SAS Token Authentication (Fallback)

🎯 Our Goal

We need to allow the frontend (React app) to upload files to Azure Blob Storage, through our backend API securely.

  • The React frontend calls our .NET Core API, which is responsible for handling file uploads.
  • The backend should authenticate with Azure Blob Storage and upload files on behalf of the frontend.
  • We must avoid using personal credentials or hardcoded connection strings.
  • We initially aimed to use Managed Identity authentication but also attempted SAS tokens due to persistent failures.

🔗 What We Have Tried

1️⃣ Using Managed Identity Authentication

We enabled System-Assigned Managed Identity for our BackendAPI (Azure App Service) and assigned it the following IAM roles:

📌 IAM Role Assignments for Managed Identity

Role Scope
Storage Blob Data Contributor /subscriptions/{subscriptionId}/resourceGroups/OmegaWrap
Storage Blob Data Owner /subscriptions/{subscriptionId}/resourceGroups/OmegaWrap/providers/Microsoft.Storage/storageAccounts/omegawrapstorage

Here is the updated Backend Code section including the ManagedIdentityCredential implementation as well as the reference to the Microsoft documentation source you originally found:


📌 Backend Code (Using Managed Identity)

We initially followed Microsoft's official documentation (link) and attempted two different credential approaches:

1️⃣ DefaultAzureCredential (Multi-Fallback Approach)

The first approach uses DefaultAzureCredential, which automatically picks the best available authentication method based on the environment.

static async Task<BlobContainerClient> GetBlobContainerClientAsync(string storageAccount, string containerName)
{
    var credential = new DefaultAzureCredential();
    var blobServiceClient = new BlobServiceClient(new Uri($"https://{storageAccount}.blob.core.windows.net"), credential);
    var blobContainerClient = blobServiceClient.GetBlobContainerClient(containerName);

    if (!await blobContainerClient.ExistsAsync())
    {
        await blobContainerClient.CreateIfNotExistsAsync(PublicAccessType.None);
    }

    return blobContainerClient;
}

2️⃣ ManagedIdentityCredential (Explicitly Targeting Managed Identity)

Since DefaultAzureCredential tries multiple methods (e.g., environment variables, CLI login, managed identity, etc.), we also tried directly using ManagedIdentityCredential, which explicitly targets Managed Identity authentication.

static async Task<BlobContainerClient> GetBlobContainerClientAsync(string storageAccount, string containerName)
{
    var credential = new ManagedIdentityCredential(); // Explicitly using Managed Identity
    var blobServiceClient = new BlobServiceClient(new Uri($"https://{storageAccount}.blob.core.windows.net"), credential);
    var blobContainerClient = blobServiceClient.GetBlobContainerClient(containerName);

    if (!await blobContainerClient.ExistsAsync())
    {
        await blobContainerClient.CreateIfNotExistsAsync(PublicAccessType.None);
    }

    return blobContainerClient;
}

📌 Source

We followed Microsoft's documentation on authenticating with System-Assigned Managed Identity:
🔗 Microsoft Docs - System-Assigned Managed Identity in .NET

Despite implementing both approaches, we continue getting the same AuthorizationFailure (403) error when our backend tries to authenticate and upload files to Azure Blob Storage.

📌 Result

Error Message (403 - AuthorizationFailure)

<Error>
  <Code>AuthorizationFailure</Code>
  <Message>This request is not authorized to perform this operation.</Message>
</Error>

Even after waiting hours for IAM role propagation, the error persists.

📌 Debugging Steps Taken:

  • Checked Managed Identity Object ID and verified it matches the role assignments.
  • Verified the token issuance using curl:
curl -H "Metadata: true" "http://169.254.169.254/metadata/identity/oauth2/token?resource=https://storage.azure.com&api-version=2019-08-01"
  • The token is issued, but requests to Blob Storage fail with AuthorizationFailure.

2️⃣ Switching to SAS Token Authentication

After struggling with Managed Identity, we generated an SAS token and attempted to authenticate the backend this way.

📌 SAS Token Generation

az storage container generate-sas \
  --account-name omegawrapstorage \
  --name uploads \
  --permissions acdlrw \
  --expiry 2025-03-01T00:00:00Z \
  --output tsv

The generated SAS token was then used in the backend:

var sasUri = $"https://{storageAccount}.blob.core.windows.net/{containerName}?{sasToken}";
var blobServiceClient = new BlobServiceClient(new Uri(sasUri));
var blobContainerClient = blobServiceClient.GetBlobContainerClient(containerName);

📌 Result

Same AuthorizationFailure (403) error persists.


🚨 Errors We Are Seeing

We continue getting the following errors regardless of the authentication method:

1️⃣ Managed Identity:

<Error>
  <Code>AuthorizationFailure</Code>
  <Message>This request is not authorized to perform this operation.</Message>
</Error>

2️⃣ SAS Token Authentication:

<Error>
  <Code>AuthorizationFailure</Code>
  <Message>This request is not authorized to perform this operation.</Message>
</Error>

💡 Questions

  1. Why is Managed Identity authentication failing even though the correct IAM roles are assigned?
  2. Is there a step we missed in setting up identity-based access for Blob Storage?
  3. Why is SAS Token authentication also returning AuthorizationFailure despite having full permissions?
  4. What other options exist for securely allowing our backend API to upload files to Azure Blob Storage?
  5. Could something be blocking authorization at the storage account level (e.g., firewall, private endpoints)?

🛠️ Additional Debugging Done

  • Checked IAM permissions for both Managed Identity and SAS authentication.
  • Generated tokens and verified their claims using jwt.ms.
  • Confirmed storage account access policies, which currently allow public network access.
  • Waited over 24 hours for IAM role propagation (no change).
  • Tried explicit DefaultAzureCredential configurations, ensuring it's using Managed Identity.

❗ Request for Help

At this point, we've exhausted all known approaches to authenticate properly with Azure Blob Storage. If anyone has successfully implemented secure backend authentication for Blob Storage in an Azure App Service environment, we would really appreciate your insights and recommendations.

Any help is greatly appreciated! 🚀


Solution

  • We are using User-Assigned Managed Identities to connect to azure resources (SQL, ACS, Keyvault, and BlobStorage) I prefer user-assigned so if I need to tear down the app service I don't have to rebuild the identity that has access, and can be reused if needed.

    Our Managed Identity has been granted Storage Blob Data Contributor via RBAC to our storage account, which I can see you already have with a system-assigned MI.

    Is your storage account configured to be private? It's been a while for me, but I think we had the same problems when we created a storage account that we made private.

    The only way we got it to work was either via a connection string (which we also did not want to use) or by configuring with vnets and a private link.

    Just to give you a quick and dirty on how we set this up:

    1. Create a Vnet with a decided address space.
    2. Create an 8-address subnet for the storage private link
    3. Create a larger subnet which I called the "dmz" - not a real dmz since it's not open to the internet
    4. In the networking blade for the storage account, set it to "Enabled from selected virtual networks and IP addresses", and then in the vnets, add your vnet.
    5. Create the private link for the storage account using the 8-address subnet we made.
    6. On your app service in the networking blade, enable Vnet integration and add the vnet/subnet (dmz) to the outbound.

    You might have to also set up a private DNS record. We had to do this because we did things a little differently than this, but my understanding is that the private link creation wizard should set up the NIC and the dns records you need so the public dns will route to the private link record tied to the IP in the subnet.