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
| Type | Description | Use Case |
|---|
| System-Assigned | Created and managed by Azure | Single function app, simple setup |
| User-Assigned | Created as a separate Azure resource | Multiple 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
- Navigate to your Function App in Azure Portal
- Select Identity from the left navigation menu
- Under System assigned, toggle status to On
- Click Save to confirm
- 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
| Role | Permissions | Use Case |
|---|
| Storage Blob Data Owner | Full control | Read, write, delete blobs |
| Storage Blob Data Contributor | Read, write, delete | General blob operations |
| Storage Blob Data Reader | Read only | Read blob content |
| Azure Service Bus Data Owner | Full control | Manage and use queues/topics |
| Azure Service Bus Data Sender | Send | Produce messages |
| Azure Service Bus Data Receiver | Receive | Consume messages |
| Cosmos DB Data Contributor | Full CRUD | Database and container operations |
| Key Vault Secrets User | Read secrets | Access 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
- Sign in to Visual Studio with your Azure account
- Tools → Options → Azure Service Authentication
- Select your account
- DefaultAzureCredential will automatically use VS credentials
VS Code
- Install Azure Account extension
- Sign in to Azure (Ctrl+Shift+P → "Azure: Sign In")
- DefaultAzureCredential will use VS Code credentials
Best Practices
Security
| Practice | Description |
|---|
| Use least privilege | Grant only required roles |
| Enable system-assigned | Simplifies identity lifecycle |
| Regular audit | Review role assignments quarterly |
| Use user-assigned for multiple apps | Share identity across apps |
Operational
| Practice | Description |
|---|
| Use DefaultAzureCredential | Automatic fallback handling |
| Configure retry policies | Handle transient failures |
| Log identity usage | Track which operations use MI |
| Monitor role assignments | Detect 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
| Error | Cause | Solution |
|---|
Azure.Identity.CredentialUnavailableException | No identity found | Enable MI on function app |
403 Forbidden | Missing role assignment | Add appropriate RBAC role |
Authentication failed | Wrong scope | Check scope matches resource |
ManagedIdentityCredential not found | Missing package | Add 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