This check flags Azure App Services that have no managed identity, which forces developers to store secrets and connection strings in code or config. Turn on a system-assigned identity with az webapp identity assign and switch your downstream resources to identity-based access.
Secrets sprawl is one of the most common ways cloud applications get compromised. An App Service that connects to a database, a storage account, or Key Vault needs credentials to do its job. The lazy path is to paste a connection string into an app setting and move on. The safer path is to let Azure handle the identity for you, so your app never holds a long-lived secret in the first place. That is exactly what a managed identity provides, and this Lensix check exists to catch App Services that skip it.
What this check detects
The appservice_nomanagedidentity check inspects each Azure App Service in your subscription and reports any that have no managed identity configured. Concretely, it looks at the identity property on the App Service resource. If that property is absent or set to None, the check fails.
Azure App Service supports two kinds of managed identity:
- System-assigned: tied directly to the App Service. It is created and deleted with the app, and only that one app can use it.
- User-assigned: a standalone Azure resource you create once and attach to one or more services. Its lifecycle is independent of any app.
The check passes if either type is present. It fails only when the app has no identity at all.
Note: A managed identity is a service principal in Microsoft Entra ID (formerly Azure AD) that Azure manages on your behalf. You never see or rotate its credentials. Azure handles token issuance and rotation behind the scenes.
Why it matters
When an App Service has no managed identity, the credentials it needs to reach other services have to live somewhere. In practice that means one of these patterns:
- A SQL or storage connection string sitting in app settings, often with an embedded password or account key.
- A client secret for a service principal hardcoded or injected as an environment variable.
- A storage account key passed around in deployment scripts and CI/CD variables.
Every one of these is a static secret. Static secrets get committed to repos by accident, copied into shared docs, logged in plaintext, and forgotten when an employee leaves. They rarely get rotated because rotation breaks things. Once leaked, they grant standing access until someone notices and revokes them.
Consider a realistic chain. An attacker finds a logging endpoint or a misconfigured app setting that exposes environment variables. Inside they find a storage account key. That key is account-wide, so it grants read and write on every container, including any backups or PII stored there. There is no MFA, no conditional access, and no expiry. The attacker now has the same blast radius as the application itself, and the only way to cut them off is to regenerate the key, which also breaks the legitimate app.
Managed identities remove the secret from the equation. The app requests a short-lived token from the Azure platform at runtime, and that token is scoped to exactly what you granted in RBAC. There is nothing to leak from config, nothing to rotate, and access can be revoked instantly by removing a role assignment.
Warning: A managed identity alone does not secure anything. You still have to grant it the right RBAC roles and switch your downstream resources off key-based or password-based auth. An identity that is never used is just unused metadata.
How to fix it
Fixing this is a two-part job: enable the identity, then grant it access to whatever the app needs.
Option 1: Enable a system-assigned identity (Azure CLI)
This is the simplest fix for an app that only needs its own identity.
az webapp identity assign \
--name my-app-service \
--resource-group my-resource-group
The command returns the principalId of the new identity. Save it, because you will use it to assign roles.
Option 2: Attach a user-assigned identity
Use this when several apps should share one identity, or when you want the identity to outlive the app.
# Create the identity once
az identity create \
--name shared-app-identity \
--resource-group my-resource-group
# Grab its resource ID
IDENTITY_ID=$(az identity show \
--name shared-app-identity \
--resource-group my-resource-group \
--query id --output tsv)
# Attach it to the App Service
az webapp identity assign \
--name my-app-service \
--resource-group my-resource-group \
--identities "$IDENTITY_ID"
Grant the identity access to a downstream resource
Say the app reads from a storage account. Assign the identity the Storage Blob Data Reader role scoped to that account.
PRINCIPAL_ID=$(az webapp identity show \
--name my-app-service \
--resource-group my-resource-group \
--query principalId --output tsv)
STORAGE_ID=$(az storage account show \
--name mystorageacct \
--resource-group my-resource-group \
--query id --output tsv)
az role assignment create \
--assignee "$PRINCIPAL_ID" \
--role "Storage Blob Data Reader" \
--scope "$STORAGE_ID"
Update the application code
The app must request tokens through the identity instead of reading a connection string. With the Azure SDKs, DefaultAzureCredential handles this automatically when a managed identity is present.
using Azure.Identity;
using Azure.Storage.Blobs;
var credential = new DefaultAzureCredential();
var client = new BlobServiceClient(
new Uri("https://mystorageacct.blob.core.windows.net"),
credential);
Once this works, delete the old connection string from app settings.
Portal steps
- Open your App Service in the Azure portal.
- Under Settings, select Identity.
- On the System assigned tab, toggle Status to On and click Save.
- Use Azure role assignments on the same page to grant scoped access to the resources the app uses.
Tip: When you assign roles, scope them as tightly as possible. Grant Storage Blob Data Reader on a single container rather than the whole subscription. The identity is only as safe as the permissions behind it.
Terraform
If you manage App Services as code, add the identity block directly to the resource.
resource "azurerm_linux_web_app" "app" {
name = "my-app-service"
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
service_plan_id = azurerm_service_plan.plan.id
identity {
type = "SystemAssigned"
}
site_config {}
}
resource "azurerm_role_assignment" "blob_reader" {
scope = azurerm_storage_account.sa.id
role_definition_name = "Storage Blob Data Reader"
principal_id = azurerm_linux_web_app.app.identity[0].principal_id
}
Danger: Do not remove old connection strings or account keys until you have confirmed the identity-based path works in a non-production slot. Pulling the secret before the app can authenticate via its identity will take the app offline.
How to prevent it from happening again
One-off fixes drift. New App Services get spun up without identities unless you make the default impossible to skip.
Enforce with Azure Policy
Azure Policy can audit or deny App Services that lack a managed identity. Here is an audit policy you can extend to deny once you are confident teams have migrated.
{
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Web/sites"
},
{
"field": "identity.type",
"notContains": "SystemAssigned"
},
{
"field": "identity.type",
"notContains": "UserAssigned"
}
]
},
"then": {
"effect": "audit"
}
}
Gate it in CI/CD
If you deploy through Terraform, fail the pipeline when an App Service module lacks an identity. A simple tfsec or checkov custom rule, or an OPA policy against the plan JSON, catches this before merge. Block the pull request rather than relying on a post-deploy scan.
Scan continuously
Run Lensix on a schedule so the appservice_nomanagedidentity check flags any app that slips past your gates, including ones created manually in the portal or by an ungoverned subscription.
Tip: Pair the policy with a remediation task. Azure Policy can run a deployIfNotExists effect that automatically enables a system-assigned identity on non-compliant App Services, so the gap closes without manual work.
Best practices
- Default to managed identity for every service-to-service call. If an Azure resource supports identity-based auth, use it. Storage, Key Vault, SQL, Service Bus, and Cosmos DB all do.
- Prefer user-assigned identities for shared workloads. They survive app redeployment and let you pre-provision role assignments, which avoids a chicken-and-egg problem in IaC pipelines.
- Scope RBAC tightly. Assign the narrowest built-in role at the smallest scope. Avoid
Contributoror subscription-level grants for application identities. - Disable key-based access where you can. Set
allowSharedKeyAccessto false on storage accounts and turn off SQL authentication in favor of Entra-only auth, so a leaked key is useless. - Audit role assignments regularly. An identity with unused permissions is the same risk as an over-privileged user. Review and trim grants as apps change.
- Use Key Vault references for the few secrets you cannot eliminate. When a third-party API forces a static key, store it in Key Vault and reference it through the managed identity rather than placing it in app settings.
Enabling a managed identity is a five-minute change, but the payoff is structural: your application stops being a place where secrets accumulate. Combine the identity with tight RBAC, key-based auth disabled downstream, and a policy that blocks new apps without one, and this entire category of secret-leak risk goes away.

