Azure Functions Managed Identity

Secure Authentication Without Secrets


Introduction

Managed Identity provides automatic, credential-free authentication to Azure services from your Azure Functions. Instead of storing connection strings, API keys, or secrets in your application code or configuration, you can leverage the built-in identity that Azure provides for your function app. This eliminates the risk of exposing credentials and simplifies the authentication architecture.

This comprehensive guide covers:

  • What is Managed Identity — Understanding the identity model
  • Enabling on Functions — Step-by-step configuration
  • Granting Access — RBAC role assignments
  • Using in Code — SDK integration patterns
  • Binding Integration — Using with triggers and bindings
  • Best Practices — Security and operational guidance

Understanding Managed Identity

How It Works

┌─────────────────────────────────────────────────────────────────────┐
│               MANAGED IDENTITY ARCHITECTURE                         │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │                    AZURE FUNCTION APP                       │   │
│   │                                                             │   │
│   │   ┌─────────────────────────────────────────────────────┐   │   │
│   │   │              SYSTEM-ASSIGNED IDENTITY               │   │   │
│   │   │   - Created automatically with the Function App     │   │   │
│   │   │   - Tied to the lifecycle of the app                │   │   │
│   │   │   - Object ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx   │   │   │
│   │   └─────────────────────────────────────────────────────┘   │   │
│   │                          │                                  │   │
│   │                          ▼                                  │   │
│   │   ┌─────────────────────────────────────────────────────┐   │   │
│   │   │           DefaultAzureCredential                    │   │   │
│   │   │   - Automatically discovers managed identity        │   │   │
│   │   │   - Falls back to other auth methods in dev         │   │   │
│   │   └─────────────────────────────────────────────────────┘   │   │
│   └─────────────────────────────────────────────────────────────┘   │
│                                │                                    │
│                                │ Azure AD Token Request             │
│                                ▼                                    │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │                      AZURE ACTIVE DIRECTORY                 │   │
│   │                                                             │   │
│   │   Validate identity → Issue access token                    │   │
│   │                                                             │   │
│   └─────────────────────────────────────────────────────────────┘   │
│                                │                                    │
│                                ▼                                    │
│   ┌────────────────┐  ┌────────────────┐  ┌────────────────┐        │
│   │   Blob Storage │  │  Service Bus   │  │   Cosmos DB    │        │
│   │                │  │    Namespace   │  │                │        │
│   └────────────────┘  └────────────────┘  └────────────────┘        │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Identity Types

TypeDescriptionUse Case
System-AssignedCreated and managed by AzureSingle function app, simple setup
User-AssignedCreated as a separate Azure resourceMultiple apps sharing one identity

Why Use Managed Identity?

┌─────────────────────────────────────────────────────────────────────┐
│                    PROBLEMS SOLVED                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ❌ Secrets in Configuration          ✅ No secrets required        │
│   ┌─────────────────────────┐        ┌─────────────────────────┐    │
│   │ "ConnectionString":      │        │ DefaultAzureCredential │    │
│   │ "Endpoint=sb://..."      │   →    │ (No credentials needed)│    │
│   └─────────────────────────┘        └─────────────────────────┘    │
│                                                                     │
│   ❌ Secret Rotation Pain             ✅ Automatic token refresh     │
│   ┌─────────────────────────┐        ┌─────────────────────────┐    │
│   │ Manual key rotation      │   →    │ Tokens auto-refresh    │    │
│   │ Downtime during rotate   │        │ Zero downtime          │    │
│   └─────────────────────────┘        └─────────────────────────┘    │
│                                                                     │
│   ❌ Audit Trail Gaps                 ✅ Azure AD audit logging      │
│   ┌─────────────────────────┐        ┌─────────────────────────┐    │
│   │ Who used the key?       │   →    │ Full identity tracking  │    │
│   │ When was it used?       │        │ Login via Azure AD      │    │
│   └─────────────────────────┘        └─────────────────────────┘    │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Enable Managed Identity on Function App

Azure Portal

  1. Navigate to your Function App in Azure Portal
  2. Select Identity from the left navigation menu
  3. Under System assigned, toggle status to On
  4. Click Save to confirm
  5. Copy the Object ID (you'll need this for role assignments)

Azure CLI

# Enable system-assigned managed identity
az functionapp update \
  --name my-function-app \
  --resource-group my-rg \
  --identity systemassigned

# Or enable during creation
az functionapp create \
  --name my-function-app \
  --resource-group my-rg \
  --storage-account mystorage \
  --plan my-app-service-plan \
  --identity systemassigned

# Get the managed identity object ID
az functionapp show \
  --name my-function-app \
  --resource-group my-rg \
  --query identity.principalId \
  --output tsv

ARM Template

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "resources": [
    {
      "type": "Microsoft.Web/sites",
      "apiVersion": "2022-09-01",
      "name": "my-function-app",
      "location": "eastus",
      "identity": {
        "type": "SystemAssigned"
      },
      "kind": "functionapp",
      "properties": {
        "serverFarmId": "/subscriptions/xxx/resourceGroups/my-rg/providers/Microsoft.Web/serverfarms/my-plan"
      }
    }
  ]
}

PowerShell

Set-AzWebApp -Name "my-function-app" -ResourceGroupName "my-rg" -IdentityType "SystemAssigned"

Grant Access to Azure Services

Storage Account Access

# Get the function app's managed identity principal ID
IDENTITY_ID=$(az functionapp show \
  --name my-function-app \
  --resource-group my-rg \
  --query identity.principalId \
  --output tsv)

# Grant Blob Data Contributor role (read/write blobs)
az role assignment create \
  --assignee $IDENTITY_ID \
  --role "Storage Blob Data Contributor" \
  --scope "/subscriptions/xxx/resourceGroups/my-rg/providers/Microsoft.Storage/storageAccounts/mystorage"

# Grant Data Reader role (read-only access)
az role assignment create \
  --assignee $IDENTITY_ID \
  --role "Storage Blob Data Reader" \
  --scope "/subscriptions/xxx/resourceGroups/my-rg/providers/Microsoft.Storage/storageAccounts/mystorage"

# Grant specific container access
az role assignment create \
  --assignee $IDENTITY_ID \
  --role "Storage Blob Data Reader" \
  --scope "/subscriptions/xxx/resourceGroups/my-rg/providers/Microsoft.Storage/storageAccounts/mystorage/blobServices/default/containers/my-container"

Service Bus Access

# Grant Send/Receive permissions
az role assignment create \
  --assignee $IDENTITY_ID \
  --role "Azure Service Bus Data Owner" \
  --scope "/subscriptions/xxx/resourceGroups/my-rg/providers/Microsoft.ServiceBus/namespaces/mynamespace"

# Or more granular roles
az role assignment create \
  --assignee $IDENTITY_ID \
  --role "Azure Service Bus Data Sender" \
  --scope "/subscriptions/xxx/resourceGroups/my-rg/providers/Microsoft.ServiceBus/namespaces/mynamespace"

az role assignment create \
  --assignee $IDENTITY_ID \
  --role "Azure Service Bus Data Receiver" \
  --scope "/subscriptions/xxx/resourceGroups/my-rg/providers/Microsoft.ServiceBus/namespaces/mynamespace"

Cosmos DB Access

# Grant Cosmos DB Data Contributor
az role assignment create \
  --assignee $IDENTITY_ID \
  --role "Cosmos DB Data Contributor" \
  --scope "/subscriptions/xxx/resourceGroups/my-rg/providers/Microsoft.DocumentDb/databaseAccounts/my-cosmos-db"

Key Vault Access

# Grant access to read secrets
az role assignment create \
  --assignee $IDENTITY_ID \
  --role "Key Vault Secrets User" \
  --scope "/subscriptions/xxx/resourceGroups/my-rg/providers/Microsoft.KeyVault/vaults/my-keyvault"

# Grant access to read certificates
az role assignment create \
  --assignee $IDENTITY_ID \
  --role "Key Vault Certificate User" \
  --scope "/subscriptions/xxx/resourceGroups/my-rg/providers/Microsoft.KeyVault/vaults/my-keyvault"

Role Definitions Reference

RolePermissionsUse Case
Storage Blob Data OwnerFull controlRead, write, delete blobs
Storage Blob Data ContributorRead, write, deleteGeneral blob operations
Storage Blob Data ReaderRead onlyRead blob content
Azure Service Bus Data OwnerFull controlManage and use queues/topics
Azure Service Bus Data SenderSendProduce messages
Azure Service Bus Data ReceiverReceiveConsume messages
Cosmos DB Data ContributorFull CRUDDatabase and container operations
Key Vault Secrets UserRead secretsAccess stored secrets

Use in Code

C# - Blob Storage

using Azure.Storage.Blobs;
using Azure.Identity;

public class BlobFunctions
{
    [FunctionName("ReadBlobWithMI")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req,
        ILogger log)
    {
        log.LogInformation("Reading blob using managed identity");
        
        // Create BlobServiceClient with DefaultAzureCredential
        var blobServiceClient = new BlobServiceClient(
            new Uri("https://mystorage.blob.core.windows.net"),
            new DefaultAzureCredential());
        
        // Get the container
        var containerClient = blobServiceClient.GetBlobContainerClient("my-container");
        
        // Get the blob
        var blobClient = containerClient.GetBlobClient("my-file.txt");
        
        // Download content
        var response = await blobClient.DownloadContentAsync();
        var content = response.Value.Content.ToString();
        
        return new OkObjectResult(new { content = content });
    }
    
    [FunctionName("WriteBlobWithMI")]
    public async Task Run(
        [QueueTrigger("write-queue")] string fileName,
        ILogger log)
    {
        var blobServiceClient = new BlobServiceClient(
            new Uri("https://mystorage.blob.core.windows.net"),
            new DefaultAzureCredential());
        
        var containerClient = blobServiceClient.GetBlobContainerClient("output");
        var blobClient = containerClient.GetBlobClient(fileName);
        
        await blobClient.UploadAsync(
            BinaryData.FromString($"Processed: {fileName}"),
            new BlobUploadOptions
            {
                Metadata = new Dictionary<string, string>
                {
                    { "processed-by", "managed-identity" },
                    { "timestamp", DateTime.UtcNow.ToString("o") }
                }
            });
        
        log.LogInformation("Blob written successfully");
    }
}

C# - Service Bus

using Azure.Messaging.ServiceBus;
using Azure.Identity;

public class ServiceBusFunctions
{
    [FunctionName("SendWithMI")]
    public async Task Run(
        [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
        ILogger log)
    {
        var body = await req.ReadAsStringAsync();
        
        // Create client with managed identity
        var client = new ServiceBusClient(
            "mynamespace.servicebus.windows.net",
            new DefaultAzureCredential());
        
        var sender = client.CreateSender("orders-queue");
        
        var message = new ServiceBusMessage(body)
        {
            ContentType = "application/json",
            Subject = "New Order",
            Properties =
            {
                { "CorrelationId", Guid.NewGuid().ToString() },
                { "Source", "function-app" }
            }
        };
        
        await sender.SendMessageAsync(message);
        
        log.LogInformation("Message sent via managed identity");
        await sender.DisposeAsync();
        await client.DisposeAsync();
    }
    
    [FunctionName("ReceiveWithMI")]
    public async Task Run(
        [ServiceBusTrigger("orders-queue")] ServiceBusReceivedMessage message,
        ILogger log)
    {
        log.LogInformation("Received: {MessageId}", message.MessageId);
        
        // Process message
        var body = message.Body.ToString();
        
        // Complete the message
        await Task.CompletedTask;
    }
}

C# - Key Vault

using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

public class KeyVaultFunctions
{
    [FunctionName("GetSecret")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
        ILogger log)
    {
        var secretName = req.Query["name"];
        
        var client = new SecretClient(
            new Uri("https://mykeyvault.vault.azure.net/"),
            new DefaultAzureCredential());
        
        var secret = await client.GetSecretAsync(secretName);
        
        return new OkObjectResult(new 
        { 
            name = secret.Value.Name,
            value = secret.Value.Value 
        });
    }
}

Python

import azure.functions as func
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient

def main(req: func.HttpRequest) -> func.HttpResponse:
    credential = DefaultAzureCredential()
    
    blob_service_client = BlobServiceClient(
        account_url="https://mystorage.blob.core.windows.net",
        credential=credential
    )
    
    container_client = blob_service_client.get_container_client("my-container")
    blob_client = container_client.get_blob_client("myfile.txt")
    
    blob_data = blob_client.download_blob()
    content = blob_data.readall()
    
    return func.HttpResponse(content, mimetype="text/plain")

JavaScript/TypeScript

const { DefaultAzureCredential } = require("@azure/identity");
const { BlobServiceClient } = require("@azure/storage-blob");

module.exports = async function (context, req) {
    const credential = new DefaultAzureCredential();
    
    const blobServiceClient = new BlobServiceClient(
        "https://mystorage.blob.core.windows.net",
        credential
    );
    
    const containerClient = blobServiceClient.getContainerClient("my-container");
    const blobClient = containerClient.getBlobClient("myfile.txt");
    
    const downloadBlockBlobResponse = await blobClient.download();
    const downloadedContent = await streamToBuffer(downloadBlockBlobResponse.readableStream);
    
    context.res = {
        body: downloadedContent.toString()
    };
};

Use with Bindings

Blob Trigger with Managed Identity

[FunctionName("BlobTriggerMI")]
public async Task Run(
    [BlobTrigger("input/{name}")] BlobClient inputBlob,
    string name,
    ILogger log)
{
    log.LogInformation($"Processing blob: {name}");
    
    // BlobClient uses managed identity automatically
    // when connection string is not provided but identity is configured
    var response = await inputBlob.DownloadContentAsync();
    var content = response.Value.Content;
    
    log.LogInformation($"Blob content length: {content.Length}");
}

Configure in local.settings.json

{
  "Values": {
    "AzureWebJobsStorage": "",
    "BlobConnection": "__storage_account_url__"
  },
  "IsEncrypted": false
}

Configuration in host.json

{
  "version": "2.0",
  "extensions": {
    "blobs": {
      "version": "3.0",
      "connection": "BlobConnection"
    }
  }
}

Service Bus Trigger with Managed Identity

[FunctionName("ServiceBusTriggerMI")]
public async Task Run(
    [ServiceBusTrigger("orders-queue", Connection = "ServiceBusConnection")] 
    ServiceBusReceivedMessage message,
    ILogger log)
{
    log.LogInformation($"Message ID: {message.MessageId}");
    
    // Process the message
    var body = message.Body.ToString();
    log.LogInformation($"Message body: {body}");
}

// Configure in Startup.cs
public class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
        builder.Services.AddSingleton<ServiceBusClient>(sp =>
        {
            var credential = new DefaultAzureCredential();
            return new ServiceBusClient(
                "mynamespace.servicebus.windows.net",
                new ServiceBusClientOptions { Credential = credential });
        });
    }
}

Local Development

VS Code Development

# Install Azure identity packages
dotnet add package Azure.Identity
npm install @azure/identity
pip install azure-identity

Environment Variables for Local Dev

// DefaultAzureCredential checks these in order:
// 1. Environment variables (AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET)
// 2. Managed Identity (when deployed to Azure)
// 3. Visual Studio
// 4. Azure CLI
// 5. Visual Studio Code

// For local development, use either:
export AZURE_CLIENT_ID="your-client-id"
export AZURE_CLIENT_SECRET="your-client-secret"
export AZURE_TENANT_ID="your-tenant-id"

# Or login with Azure CLI
az login

Visual Studio

  1. Sign in to Visual Studio with your Azure account
  2. Tools → Options → Azure Service Authentication
  3. Select your account
  4. DefaultAzureCredential will automatically use VS credentials

VS Code

  1. Install Azure Account extension
  2. Sign in to Azure (Ctrl+Shift+P → "Azure: Sign In")
  3. DefaultAzureCredential will use VS Code credentials

Best Practices

Security

PracticeDescription
Use least privilegeGrant only required roles
Enable system-assignedSimplifies identity lifecycle
Regular auditReview role assignments quarterly
Use user-assigned for multiple appsShare identity across apps

Operational

PracticeDescription
Use DefaultAzureCredentialAutomatic fallback handling
Configure retry policiesHandle transient failures
Log identity usageTrack which operations use MI
Monitor role assignmentsDetect unauthorized access

Performance

// Good: Create client once
public class MyFunctions
{
    private readonly BlobServiceClient _blobClient;
    
    public MyFunctions()
    {
        _blobClient = new BlobServiceClient(
            new Uri("https://mystorage.blob.core.windows.net"),
            new DefaultAzureCredential());
    }
    
    // Reuse the client - tokens are cached
}

// Avoid: Creating client per invocation
[FunctionName("BadExample")]
public async Task Run([BlobTrigger("container/{name}")] BlobClient blob)
{
    // This creates new credential each time - inefficient
    var client = new BlobServiceClient(
        new Uri("https://mystorage.blob.core.windows.net"),
        new DefaultAzureCredential());
}

Troubleshooting

Common Errors

ErrorCauseSolution
Azure.Identity.CredentialUnavailableExceptionNo identity foundEnable MI on function app
403 ForbiddenMissing role assignmentAdd appropriate RBAC role
Authentication failedWrong scopeCheck scope matches resource
ManagedIdentityCredential not foundMissing packageAdd Azure.Identity package

Debug Steps

# 1. Verify identity is enabled
az functionapp show \
  --name my-function-app \
  --resource-group my-rg \
  --query identity

# 2. Verify role assignments
az role assignment list \
  --assignee <principal-id> \
  --output table

# 3. Test access manually
az storage blob list \
  --account-name mystorage \
  --container-name mycontainer \
  --auth-mode login

# 4. Check Application Insights
az monitor app-insights query \
  --app my-app \
  --analytics "requests | where operation_Name contains 'Blob' | take 10"

Network Considerations

// If using private endpoints, ensure identity can reach them
var options = new DefaultAzureCredentialOptions
{
    ManagedIdentityClientId = "<user-assigned-client-id>"
};

var client = new BlobServiceClient(
    new Uri("https://mystorage.blob.core.windows.net"),
    new DefaultAzureCredential(options));

Related Topics


Azure Integration Hub - Intermediate Level