Back to blog
AzureBest PracticesCloud SecurityIdentity & AccessOperations & Compliance

Service Bus Namespace Local Authentication Enabled

Learn why Azure Service Bus SAS key authentication is risky, how to migrate clients to Azure AD, and how to disable local auth with CLI, Terraform, and Azure Policy.

TL;DR

This check flags Service Bus namespaces that still accept SAS key authentication, which means anyone holding a connection string can read and write messages without an Azure AD identity. Disable local auth and switch clients to Azure AD (Entra ID) tokens with az servicebus namespace update --disable-local-auth true.

SAS keys are the path of least resistance for getting messages flowing through Azure Service Bus, which is exactly why they end up everywhere. They get baked into app settings, copied into Terraform state, pasted into Slack, and forgotten in CI logs. Every one of those copies is a standing credential that grants access to your messaging plane, and none of them are tied to an identity you can audit or revoke cleanly.

This check tells you which namespaces still allow that. When disableLocalAuth is false, the namespace honors SAS-based connection strings alongside (or instead of) Azure AD authentication. Turning local auth off forces every client to authenticate with an Entra ID identity.


What this check detects

The check inspects each Azure Service Bus namespace and reads the disableLocalAuth property. If it is set to false, the namespace is flagged.

Local authentication refers to Shared Access Signature (SAS) authorization. Service Bus generates Shared Access Policies (the default is RootManageSharedAccessKey) that produce primary and secondary keys. Those keys get turned into connection strings like this:

Endpoint=sb://my-namespace.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=abc123...=

Anyone with that string can send and receive messages on the namespace. The key is not tied to a user, a managed identity, or any auditable principal. It is just a secret.

Note: Service Bus supports two authorization models. SAS (local auth) uses signed tokens derived from a shared key. Azure RBAC uses Entra ID identities with roles like Azure Service Bus Data Sender and Azure Service Bus Data Receiver. This check is about removing the first so you rely entirely on the second.


Why it matters

SAS keys are long-lived bearer tokens. If a key leaks, an attacker has full access until someone manually rotates it, and rotation breaks every client still using the old key. That tension means keys often go years without being changed.

Here is how this turns into a real incident:

  • Credential sprawl. The RootManageSharedAccessKey grants Manage, Send, and Listen on the entire namespace. Developers reach for it because it works everywhere, so one over-privileged key ends up in dozens of places.
  • No identity, no audit trail. When a SAS key sends a message, the logs show the namespace was accessed, not who accessed it. During an incident response, you cannot tell a compromised key from a legitimate service.
  • Leaked connection strings. Connection strings show up in client-side bundles, container images, CI/CD logs, and public repositories. A scan of GitHub for servicebus.windows.net regularly turns up live credentials.
  • Painful revocation. Revoking a leaked key means regenerating it, which immediately breaks every workload using it. Teams hesitate, and the window of exposure stretches out.

With Azure AD authentication, every client uses a token bound to a managed identity or service principal. Access is granted through RBAC role assignments you can list, scope, and remove. Revoking access is a role assignment removal, not a fire drill that breaks production.

Warning: Disabling local auth breaks any client still using a connection string. Inventory every consumer and producer before you flip the switch, and migrate them to Azure AD first. See the rollout sequence below.


How to fix it

The fix is to migrate clients to Azure AD authentication, then disable local auth on the namespace. Do it in that order to avoid an outage.

Step 1: Assign data plane roles to your workloads

Grant each workload identity the least-privilege role it needs. Use Data Sender for producers and Data Receiver for consumers. Avoid Data Owner unless a workload genuinely needs both plus management.

# Get the principal ID of your app's managed identity
PRINCIPAL_ID=$(az identity show \
  --name my-app-identity \
  --resource-group my-rg \
  --query principalId -o tsv)

# Scope to a specific namespace
NAMESPACE_ID=$(az servicebus namespace show \
  --name my-namespace \
  --resource-group my-rg \
  --query id -o tsv)

# Sender role for a producer
az role assignment create \
  --assignee "$PRINCIPAL_ID" \
  --role "Azure Service Bus Data Sender" \
  --scope "$NAMESPACE_ID"

You can scope roles to a single queue or topic instead of the whole namespace for tighter control. Append /queues/my-queue or /topics/my-topic to the scope.

Step 2: Update your client code to use Azure AD

Replace connection strings with DefaultAzureCredential. Here is a .NET example:

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

var client = new ServiceBusClient(
    "my-namespace.servicebus.windows.net",
    new DefaultAzureCredential());

ServiceBusSender sender = client.CreateSender("my-queue");
await sender.SendMessageAsync(new ServiceBusMessage("hello"));

And the equivalent in Python:

from azure.servicebus import ServiceBusClient
from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()
client = ServiceBusClient(
    fully_qualified_namespace="my-namespace.servicebus.windows.net",
    credential=credential,
)

Step 3: Disable local auth on the namespace

Once every client authenticates with Azure AD, turn off local auth.

Danger: This command immediately rejects every SAS-based connection. Any client still using a connection string will fail with Unauthorized the moment it reconnects. Confirm your migration is complete in a non-production environment first.

az servicebus namespace update \
  --name my-namespace \
  --resource-group my-rg \
  --disable-local-auth true

Verify the change:

az servicebus namespace show \
  --name my-namespace \
  --resource-group my-rg \
  --query disableLocalAuth -o tsv
# should return: true

Console steps

  1. Open the Azure portal and go to your Service Bus namespace.
  2. Under Settings, select Shared access policies.
  3. Find the Local Authentication toggle near the top and set it to Disabled.
  4. Save. New SAS-based connections will be rejected.

Tip: DefaultAzureCredential walks a chain of credential sources (environment variables, managed identity, Azure CLI login, and more). This means the same code works locally with your developer login and in production with a managed identity, with no connection strings to manage in either place.


Fixing it with Infrastructure as Code

Set local auth disabled at creation time so namespaces are never exposed in the first place.

Terraform

resource "azurerm_servicebus_namespace" "main" {
  name                = "my-namespace"
  location            = azurerm_resource_group.main.location
  resource_group_name = azurerm_resource_group.main.name
  sku                 = "Standard"

  local_auth_enabled = false
}

resource "azurerm_role_assignment" "sender" {
  scope                = azurerm_servicebus_namespace.main.id
  role_definition_name = "Azure Service Bus Data Sender"
  principal_id         = azurerm_user_assigned_identity.app.principal_id
}

Bicep

resource sbNamespace 'Microsoft.ServiceBus/namespaces@2022-10-01-preview' = {
  name: 'my-namespace'
  location: location
  sku: {
    name: 'Standard'
  }
  properties: {
    disableLocalAuth: true
  }
}

How to prevent it from happening again

Manual remediation does not scale. Enforce the policy so non-compliant namespaces get blocked or flagged automatically.

Azure Policy

Azure ships a built-in policy that targets exactly this: "Service Bus namespaces should have local authentication methods disabled." Assign it with a Deny effect to block new namespaces that enable local auth, or Audit to report on existing ones.

# Built-in policy definition ID
POLICY_ID="/providers/Microsoft.Authorization/policyDefinitions/cfb11c26-bc54-4d7c-9b07-7c0f6d44bf80"

az policy assignment create \
  --name "deny-servicebus-local-auth" \
  --display-name "Deny Service Bus local authentication" \
  --policy "$POLICY_ID" \
  --scope "/subscriptions/" \
  --params '{"effect":{"value":"Deny"}}'

Warning: A Deny effect only stops new or updated resources. It will not remediate the namespaces you already have. Run an audit pass first, fix existing namespaces, then tighten to Deny so you do not surprise an in-flight deployment.

CI/CD gates

Catch the misconfiguration before it reaches Azure. Run a policy-as-code scan in your pipeline against Terraform plans or Bicep templates. Tools like Checkov, tfsec, and Conftest can assert that local_auth_enabled = false on every Service Bus namespace.

A simple Conftest rule looks like this:

package main

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "azurerm_servicebus_namespace"
  resource.change.after.local_auth_enabled == true
  msg := sprintf("Service Bus namespace '%s' has local auth enabled", [resource.address])
}

Wire that into your pull request checks so a reviewer never has to remember this rule by hand.


Best practices

  • Use Azure AD everywhere. Managed identities for Azure workloads, service principals for external systems. No connection strings in app settings.
  • Scope roles tightly. Assign Data Sender and Data Receiver at the queue or topic level rather than the namespace, and never use Data Owner for a runtime workload.
  • Delete unused SAS policies. If you must keep local auth temporarily, remove the broad RootManageSharedAccessKey from runtime use and create narrowly scoped policies instead.
  • Enable diagnostic logging. Send Service Bus operational logs to a Log Analytics workspace so you have an identity-attributed audit trail of who sent and received what.
  • Make it the default. Bake disableLocalAuth: true into your namespace modules so every new namespace is secure without anyone thinking about it.

Tip: Run this same check across Event Hubs and Azure Storage. Both support a disableLocalAuth (or equivalent shared-key) setting, and the same migration pattern applies. Fixing one service is a good moment to sweep the rest.