← Back to Tutorials
Intermediate⏱️ 25 min

Secure Access with Managed Identity (No Connection Strings)

Secure Access with Managed Identity (No Connection Strings)

Why Avoid Connection Strings?

RiskConnection StringsManaged Identity
Secret rotationManual, error-proneAutomatic (Azure handles tokens)
Exposure in code/configHigh (leaks in repos, logs)None (no secret to leak)
Blast radiusFull namespace accessScoped RBAC roles
Audit trailLimitedFull Azure AD audit logs
ComplianceFails many auditsMeets 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"

Screenshot: Managed Identity enabled in Function App

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
RolePermissionsUse Case
Azure Service Bus Data SenderSend messagesProducer apps
Azure Service Bus Data ReceiverReceive/delete messagesConsumer apps
Azure Service Bus Data OwnerFull data operationsAdmin scenarios

Screenshot: RBAC role assignments on Service Bus namespace

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

Screenshot: Successful message processing with Managed Identity

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
  • DefaultAzureCredential provides a seamless local-to-production experience
  • RBAC roles offer fine-grained, auditable access control
  • Always assign the least privilege role needed

← Previous: Message Sessions | Next: Retry Patterns →