Secure Access with Managed Identity (No Connection Strings)
Why Avoid Connection Strings?
| Risk | Connection Strings | Managed Identity |
|---|---|---|
| Secret rotation | Manual, error-prone | Automatic (Azure handles tokens) |
| Exposure in code/config | High (leaks in repos, logs) | None (no secret to leak) |
| Blast radius | Full namespace access | Scoped RBAC roles |
| Audit trail | Limited | Full Azure AD audit logs |
| Compliance | Fails many audits | Meets zero-trust requirements |
Architecture Overview
┌────────────────────┐ ┌──────────────────┐
│ Function App / │ Token │ Azure Active │
│ Logic App │◀───────▶│ Directory │
│ (System MI) │ └──────────────────┘
└────────┬───────────┘ │
│ Bearer Token │ RBAC
▼ ▼
┌────────────────────────────────────────────────┐
│ Azure Service Bus Namespace │
│ Role: Azure Service Bus Data Sender/Receiver │
└────────────────────────────────────────────────┘
Prerequisites
- Azure Service Bus namespace (Standard or Premium)
- Azure Function App or Logic App (Standard)
- Azure CLI installed
- .NET 8 SDK
Step 1: Enable Managed Identity on Function App
RESOURCE_GROUP="rg-servicebus-tutorials"
FUNCTION_APP="func-orders-processor"
# Enable system-assigned managed identity
az functionapp identity assign \
--resource-group $RESOURCE_GROUP \
--name $FUNCTION_APP
# Capture the principal ID
PRINCIPAL_ID=$(az functionapp identity show \
--resource-group $RESOURCE_GROUP \
--name $FUNCTION_APP \
--query principalId -o tsv)
echo "Principal ID: $PRINCIPAL_ID"

Step 2: Grant Azure Service Bus RBAC Roles
NAMESPACE="sb-tutorials-ns"
# Get the Service Bus namespace resource ID
SB_RESOURCE_ID=$(az servicebus namespace show \
--resource-group $RESOURCE_GROUP \
--name $NAMESPACE \
--query id -o tsv)
# Assign "Azure Service Bus Data Sender" role
az role assignment create \
--assignee $PRINCIPAL_ID \
--role "Azure Service Bus Data Sender" \
--scope $SB_RESOURCE_ID
# Assign "Azure Service Bus Data Receiver" role
az role assignment create \
--assignee $PRINCIPAL_ID \
--role "Azure Service Bus Data Receiver" \
--scope $SB_RESOURCE_ID
| Role | Permissions | Use Case |
|---|---|---|
| Azure Service Bus Data Sender | Send messages | Producer apps |
| Azure Service Bus Data Receiver | Receive/delete messages | Consumer apps |
| Azure Service Bus Data Owner | Full data operations | Admin scenarios |

Step 3: Update Code to Use DefaultAzureCredential
Install the required NuGet package:
dotnet add package Azure.Identity
Update your Service Bus client code:
using Azure.Identity;
using Azure.Messaging.ServiceBus;
var fullyQualifiedNamespace = "sb-tutorials-ns.servicebus.windows.net";
var queueName = "orders-queue";
// DefaultAzureCredential automatically uses:
// - Managed Identity in Azure
// - Azure CLI credential locally
// - Visual Studio credential in IDE
var credential = new DefaultAzureCredential();
await using var client = new ServiceBusClient(fullyQualifiedNamespace, credential);
await using var sender = client.CreateSender(queueName);
var message = new ServiceBusMessage("Order created via Managed Identity");
await sender.SendMessageAsync(message);
Console.WriteLine("Message sent using Managed Identity!");
Step 4: Test Locally with Azure CLI Credential
DefaultAzureCredential falls back to your Azure CLI login when running locally:
# Log in to Azure CLI
az login
# Verify your account
az account show --query user.name -o tsv
# Assign yourself the same roles for local testing
USER_OBJECT_ID=$(az ad signed-in-user show --query id -o tsv)
az role assignment create \
--assignee $USER_OBJECT_ID \
--role "Azure Service Bus Data Sender" \
--scope $SB_RESOURCE_ID
az role assignment create \
--assignee $USER_OBJECT_ID \
--role "Azure Service Bus Data Receiver" \
--scope $SB_RESOURCE_ID
┌─────────────────────────────────────────────────────────┐
│ DefaultAzureCredential Chain │
├─────────────────────────────────────────────────────────┤
│ 1. EnvironmentCredential (env vars) │
│ 2. WorkloadIdentityCredential (Kubernetes) │
│ 3. ManagedIdentityCredential (Azure VMs/App Service) │
│ 4. AzureCliCredential (local dev) ◀── Used locally │
│ 5. VisualStudioCredential │
│ 6. InteractiveBrowserCredential │
└─────────────────────────────────────────────────────────┘
Step 5: Verify in Production
After deploying, confirm the identity is working:
# Check Function App logs for auth errors
az functionapp log tail \
--resource-group $RESOURCE_GROUP \
--name $FUNCTION_APP
# Verify role assignments
az role assignment list \
--assignee $PRINCIPAL_ID \
--scope $SB_RESOURCE_ID \
--output table

Step 6: Remove Connection Strings from Configuration
# Remove the connection string app setting
az functionapp config appsettings delete \
--resource-group $RESOURCE_GROUP \
--name $FUNCTION_APP \
--setting-names "ServiceBusConnectionString"
# Add the namespace (not a secret)
az functionapp config appsettings set \
--resource-group $RESOURCE_GROUP \
--name $FUNCTION_APP \
--settings "ServiceBusNamespace=sb-tutorials-ns.servicebus.windows.net"
Key Takeaways
- Managed Identity eliminates secrets from your code and configuration
DefaultAzureCredentialprovides a seamless local-to-production experience- RBAC roles offer fine-grained, auditable access control
- Always assign the least privilege role needed