Skip to main content
emnode
Compliance

Use managed identities instead of stored secrets

One capability across web apps and function apps: stop authenticating to Azure services with a connection string or client secret baked into config, and let the platform issue and rotate the credential for you so there is nothing left to leak.

14 min·10 sections·AZURE

Last reviewed

Managed identities: the basics

What does an app authenticating with a stored secret actually look like?

When an Azure App Service web app or a function app needs to reach another Azure service, a SQL database, a storage account, a Key Vault, a Service Bus queue, it has to prove who it is. The old way is to hand it a secret: a SQL connection string with a password, a storage account key, or a service principal client ID and secret dropped into application settings. That secret now lives in config, in deployment pipelines, in the engineers' heads and often in a few places nobody remembers. A managed identity replaces all of that. Azure gives the app its own identity in Microsoft Entra ID, and the app asks the platform for a short-lived token at runtime instead of carrying a long-lived secret.

Microsoft Defender for Cloud surfaces this as two closely related recommendations: 'Managed identity should be used in web apps' and 'Managed identity should be used in function apps'. They map to the Microsoft Cloud Security Benchmark control IM-3, manage application identities securely and automatically. They read as two findings, one per compute type, but they are a single capability: every app and function that talks to Azure should do it as itself, with a platform-issued credential, not with a secret a human had to create, store and remember to rotate.

Most of this is inertia rather than intent. An app was scaffolded with a connection string because that is what the quickstart showed, a function was wired to storage with an account key because it was the fastest path, a service principal was created for one integration and its secret quietly outlived the reason for it. The work is to find every web app and function app that has no managed identity assigned, give each one an identity, grant that identity the exact access it needs on the target service, and then delete the stored secret it used to rely on.

In this lesson you will learn how Azure App Service and Functions authenticate to other Azure services, why a managed identity is stronger than any stored secret, how to find every app and function that still lacks one, and how to switch them over without an outage. The Controls this lesson covers section lists every Defender for Cloud recommendation in this capability, each linking to a deep page with the exact check and a copy-and-paste fix.

Fun fact

The secret that outlived everyone who knew about it

A striking share of cloud breaches start not with a clever exploit but with a credential someone left lying around: a key committed to a public repository, a connection string in an error message, a service principal secret created for a one-off task that nobody ever revoked. Automated scanners crawl public code and exposed logs for exactly these strings around the clock, and a live secret is often found within minutes of being exposed. The whole point of a managed identity is that there is no such string to find: the credential is a certificate the Azure platform holds, rotates roughly every six weeks, and never writes into your app's config in the first place.

Finding apps that still authenticate with stored secrets

Priya is the platform lead at a fintech preparing for its first SOC 2 audit. Defender for Cloud flags managed-identity findings across both web apps and function apps in two subscriptions that pre-date the team's current guardrails.

Rather than work the findings one by one, Priya starts by listing which apps and functions have no identity assigned at all, so the ones holding real production secrets can be separated from harmless demo apps before anything changes.

Start by listing every web app and function app and whether it has a managed identity assigned. The ones reporting None are the findings.

$ az webapp list --query "[].{name:name, identity:identity.type}" -o table
Name Identity
------------------ --------------
payments-api-prod None
ledger-fn-prod None
marketing-demo SystemAssigned
# payments-api-prod and ledger-fn-prod authenticate with a stored secret. Fix these first.

An app with identity None is still proving who it is with a connection string or key. The production apps that hold sensitive secrets are the highest-value targets in this group.

How a managed identity replaces a stored secretdeep dive

When you enable a managed identity on a web app or function app, Azure creates a service principal for it in Microsoft Entra ID and injects a token endpoint into the app's runtime, exposed through the IDENTITY_ENDPOINT and IDENTITY_HEADER environment variables (the older MSI_ENDPOINT and MSI_SECRET on legacy stacks). At runtime the app makes a local HTTP call to that endpoint and receives a short-lived Entra ID access token for the target resource. The Azure Identity client libraries (DefaultAzureCredential and ManagedIdentityCredential) wrap this so the code never sees a secret at all. The crucial property is that this endpoint is link-local: it only answers from inside the app, so a token request cannot be replayed from an attacker's laptop the way a stolen connection string can.

The credential behind the identity is a certificate that the Azure platform creates, stores and rotates for you, typically rolling roughly every six weeks, with no action on your part. Contrast that with a service principal client secret, which you create, paste into config, and have to remember to rotate before it expires (or, worse, set never to expire). A managed identity has no secret you can read, copy, log or check into a repository. There is also nothing to rotate manually and nothing to leak. A token request only works from the resource the identity belongs to.

There are two flavours. A system-assigned identity is created with the app, shares its lifecycle and is deleted when the app is, ideal for a one-to-one relationship. A user-assigned identity is a standalone resource you can attach to several apps, which suits a fleet that should share one set of role assignments. Either way, the identity is only half the job: you must also grant it least-privilege access on the target service, an Azure RBAC role assignment for storage or Key Vault data-plane access, or a contained database user for Azure SQL. Defender clears the recommendation once an identity is assigned, but the access grant is what actually lets you delete the old secret.

What is the impact of leaving apps on stored secrets?

The direct impact is a standing, leakable credential per app. A connection string or account key works from anywhere, for anyone who obtains it, until a human notices and rotates it. Secrets leak in mundane ways: committed to a repository, printed in an exception, pasted into a chat, left in a screenshot, or simply carried in config that more people can read than should. A service principal secret set never to expire is a permanent skeleton key. Every app still on a stored secret is one careless moment away from handing an attacker exactly the access that secret grants.

The second-order impact is rotation debt and blast radius. Stored secrets have to be rotated, and rotation is manual, risky and easy to defer, so old secrets accumulate and outlive the reason they were created. When you finally do have to rotate one in a hurry, after a suspected leak, you discover how many apps and pipelines depend on it. A managed identity removes that entire burden: there is no secret to rotate, the platform rolls the underlying credential automatically, and the access an identity holds is scoped and revocable in one place.

On the compliance side, every modern framework, ISO 27001, SOC 2, HIPAA, PCI DSS and the UK and EU data-protection regimes, expects evidence that application credentials are managed securely and rotated, and that secrets are not hard-coded. The Microsoft Cloud Security Benchmark control IM-3 says exactly this: manage application identities securely and automatically. A passing set of managed-identity recommendations across every subscription is the cheapest and most defensible artefact you can hand an auditor on the question of how your apps authenticate.

How do you move apps to managed identities safely?

Work the capability as one loop rather than chasing individual findings. The order matters: grant the identity its access and prove the new path works before you delete the old secret, so you never take a live app offline.

1. Inventory every app and function by identity

List the web apps and function apps that report an identity of None, and for each note which Azure services it talks to and what stored secret it uses today: a SQL connection string, a storage key, a Service Bus connection string, a service principal client secret. Treat this inventory as the source of truth, not the Defender finding count, because each compute type surfaces as its own recommendation.

2. Prioritise by blast radius, not by count

Most of these are inertia. For each app, decide what its current secret can reach. A production app holding a database password or a storage account key is a far higher-value target than a demo app. Switch the apps that touch sensitive data first, and treat the throwaway ones as low-priority cleanup.

3. Assign an identity, grant least privilege, then swap and delete the secret

Enable a system-assigned identity (or attach a shared user-assigned one), then grant it the exact role it needs on the target service: Storage Blob Data Contributor on the one container it uses, a Key Vault data-plane role, or a contained user in Azure SQL, never Owner or Contributor on the subscription. Update the code to use DefaultAzureCredential, confirm the new path works, and only then remove the connection string or key from application settings.

4. Ratchet it shut with Azure Policy

Assign the built-in audit policies 'App Service apps should use managed identity' and 'Function apps should use managed identity' so any new app or function created without one is flagged immediately, and pair them with the policies that audit for connection strings and authentication settings. This turns a one-off cleanup into a standing guardrail, so the safe configuration is enforced rather than merely encouraged.

# 1. Give the app its own identity and capture the principal ID.
principalId=$(az webapp identity assign \
  --resource-group payments-rg \
  --name payments-api-prod \
  --query principalId -o tsv)

# 2. Grant least privilege on the exact resource it uses (not the subscription).
az role assignment create \
  --assignee-object-id "$principalId" \
  --assignee-principal-type ServicePrincipal \
  --role "Storage Blob Data Contributor" \
  --scope "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/payments-rg/providers/Microsoft.Storage/storageAccounts/paymentsdata"

# 3. Only after the code uses DefaultAzureCredential and the new path is verified,
#    delete the stored secret so there is nothing left to leak.
az webapp config appsettings delete \
  --resource-group payments-rg --name payments-api-prod \
  --setting-names StorageConnectionString

# 4. Ratchet it shut: audit any future app created without a managed identity.
az policy assignment create \
  --name audit-webapp-managed-identity \
  --policy 2b9ad585-36bc-4615-b300-fd4435808332 \
  --scope "/subscriptions/00000000-0000-0000-0000-000000000000"

Quick quiz

Question 1 of 5

Defender for Cloud shows 'Managed identity should be used in web apps' and 'Managed identity should be used in function apps' across several resources. What is the most efficient way to think about them?

You can now treat application authentication as one capability rather than a scatter of findings: inventory every web app and function that still proves who it is with a stored secret, prioritise by what that secret can reach, give each one an identity with least-privilege access, swap the code over and delete the secret, then ratchet the estate shut with Azure Policy. The Controls this lesson covers section below links every recommendation in this group to its deep page and fix.

Back to the library

Controls this lesson covers

One capability, many Microsoft Defender for Cloud recommendations. This lesson is the shared playbook; each control below keeps its own deep page with the exact check, severity and a copy-and-paste fix.