Azure Security

Secure Passwordless Access: Connecting Azure Web App to Blob Storage with User-Assigned Managed Identity

Author Sarah Johnson
June 25, 2025 8 min read

When building AI applications on Azure, securely accessing Blob Storage from your Web App is critical. While Microsoft's documentation covers managed identities, the practical implementation details for a bulletproof setup are often scattered or unclear. After extensive trial and error, we've perfected a secure, passwordless connection method using User-Assigned Managed Identity. This blog post focused on helping you, if you are struggling with the same issues.

This guide reveals the exact ARM template configuration and Python code that solved our production challenges, ensuring zero credentials in your code and maximum security for your Azure Web App (or other App instance) file operations.

Q: Why use User-Assigned Managed Identity instead of connection strings?

A: User-Assigned Managed Identity eliminates credential management risks, automatically rotates secrets, and provides granular Azure RBAC control. It's the most secure way for Azure services to communicate with each other.

The Problem: Microsoft's Documentation Gaps

Microsoft's documentation on User-Assigned Managed Identity is technically accurate but misses critical practical details:

  • Multiple role assignments are needed at different scopes
  • The exact ARM template structure for User-Assigned Identity isn't clearly shown
  • Python SDK authentication nuances aren't explained
  • Luck of the single guide. It looks, that different people were working on it, but no-one had a clear view on whole implementation
  • At Azure UI it works only if you are adding the correct assigment inside Managed Identity at (preview)
  • It works manually at Azure UI but NOT for deployment automation using ARM template or Terraform

Warning: Missing These Steps Causes Failures

We spent weeks troubleshooting authentication errors before discovering all required role assignments must be explicitly declared in the ARM template for deployment automation.

The Solution: Complete ARM Template

Here's the battle-tested ARM template that properly configures User-Assigned Managed Identity with all necessary permissions:

{
    "type": "Microsoft.Storage/storageAccounts/blobServices/containers",
    "apiVersion": "2023-04-01",
    "name": "[concat(variables('your-unique-resource-identity-name'), '/default/your-blob-storage-container-name')]",
    "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('your-unique-resource-identity-name'), 'default')]",
        "[resourceId('Microsoft.Storage/storageAccounts', variables('your-unique-resource-identity-name'))]"
    ],
    "properties": {
        "immutableStorageWithVersioning": {
            "enabled": false
        },
        "defaultEncryptionScope": "$account-encryption-key",
        "denyEncryptionScopeOverride": false,
        "publicAccess": "None"
    }
},
{
    "type": "Microsoft.Authorization/roleAssignments",
    "apiVersion": "2022-04-01",
    "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', variables('your-unique-resource-identity-name')), 'Storage Blob Data Contributor')]",
    "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', variables('your-unique-resource-identity-name'))]"
    ],
    "properties": {
        "roleDefinitionId": "[concat(subscription().id, '/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe')]",
        "principalId": "[reference(variables('identityResourceID')).principalId]",
        "principalType": "ServicePrincipal"
    }
},
{
    "type": "Microsoft.Authorization/roleAssignments",
    "apiVersion": "2022-04-01",
    "name": "[guid(concat(variables('your-unique-resource-identity-name'), 'storage-account-scope'), 'Storage Blob Data Contributor')]",
    "scope": "[resourceId('Microsoft.Storage/storageAccounts', variables('your-unique-resource-identity-name'))]",
    "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', variables('your-unique-resource-identity-name'))]"
    ],
    "properties": {
        "roleDefinitionId": "[concat(subscription().id, '/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe')]",
        "principalId": "[reference(variables('identityResourceID')).principalId]",
        "principalType": "ServicePrincipal"
    }
},
{
    "type": "Microsoft.Authorization/roleAssignments",
    "apiVersion": "2022-04-01",
    "name": "[guid(resourceId('Microsoft.Storage/storageAccounts', variables('your-unique-resource-identity-name')), 'system-assigned-msi', 'Storage Blob Data Contributor')]",
    "scope": "[resourceId('Microsoft.Storage/storageAccounts', variables('your-unique-resource-identity-name'))]",
    "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', variables('your-unique-resource-identity-name'))]"
    ],
    "properties": {
        "roleDefinitionId": "[concat(subscription().id, '/providers/Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe')]",
        "principalId": "[reference(resourceId('Microsoft.Web/sites', variables('your-unique-resource-identity-name')), '2022-03-01', 'Full').identity.principalId]",
        "principalType": "ServicePrincipal"
    }
}

Key ARM Template Insights:

  1. Container configuration with secure defaults
  2. Three roles assignments at different scopes
  3. Proper dependency chains between resources
  4. Both User-Assigned and System-Assigned identity support

The Python Implementation

With the infrastructure properly configured, here's the clean Python code that leverages DefaultAzureCredential to authenticate securely:

# Save to azure blob storage
blob_path = file_path.split("/file-path/file-path/")[1]
account_name = os.getenv("AZURE_STORAGE_ACCOUNT", "")
container_name = os.getenv("AZURE_CONTAINER_NAME", "")
account_url = f"https://{account_name}.blob.core.windows.net"

credential = DefaultAzureCredential()
blob_service_client = BlobServiceClient(account_url=account_url, credential=credential)

container_client = blob_service_client.get_container_client(container_name)
blob_client = container_client.get_blob_client(blob_path)

with open(file_path, "rb") as data:
                        blob_client.upload_blob(data, overwrite=True)

Why This Works:

  • DefaultAzureCredential automatically discovers the Managed Identity
  • No secrets or connection strings in code
  • Supports automation on scale where you need create Azure Web Apps, Azure Blob Storage fast and secure
  • AZURE_STORAGE_ACCOUNT and AZURE_CONTAINER_NAME all the App needs to configure secure connection. No SAS token needed!

Step-by-Step Deployment Guide

1

To validate ARM template in development

In Azure Portal, Search for Deploy a custom template at Azure.

2

Deploy ARM Template

Use the your ARM template with your Azure Web App + Azure Blob storage creation + append it with ARM template provided above to configure storage and permissions.

3

Deploy your ARM template to check errors (if any)

Once your ARM template works at Deploy a custom template at Azure, you can integrate it into productions app deployment workflows

4

Implement Python Code

Use the Python snippet with DefaultAzureCredential for secure access.

Common Pitfalls and Solutions

Error: 403 Forbidden

Solution: Verify all role assignments in ARM template were applied correctly. Check the exact scope of each assignment.

Error: AuthorizationPermissionMismatch

Solution: Add Microsoft.Authorization/roleDefinitions/ba92f5b4-2d11-453d-a403-e96b0029c9fe role assigment.

This request is not authorized to perform this operation using this permission.

Solution: Make sure your ARM template creates Managed Identity with your instances connection as well as proper role assigment.

By following this exact configuration, you'll achieve the most secure possible connection between your Azure Web App and Blob Storage, with zero credentials in your code and automatic secret rotation handled by Azure.

Eliminate credential risks in your AI Agent applications today.

Sarah Johnson

Sarah Johnson

Azure Security Architect at NLSQL

Sarah specializes in secure cloud architectures and identity management patterns for enterprise AI applications.