Managed Identity¶
Use App Service managed identity with Azure.Identity and DefaultAzureCredential to access Azure resources without storing credentials.
flowchart TD
A[Enable system-assigned identity] --> B[Get principal ID]
B --> C[Grant RBAC on target resource]
C --> D[Use DefaultAzureCredential in code]
D --> E[Request token at runtime]
E --> F[Access Azure service] How RBAC Connects Identity to Resources¶
A managed identity alone does not grant access. Azure RBAC binds three elements into a role assignment:
flowchart TD
P[Principal<br/>Who: Managed Identity or Service Principal] --> RA[Role Assignment<br/>Unique GUID per binding]
RD[Role Definition<br/>What: Key Vault Secrets User, Storage Blob Data Reader, etc.] --> RA
S[Scope<br/>Where: Subscription, Resource Group, or Resource] --> RA
RA --> ACCESS[Access Granted]
style RA fill:#f5c542,stroke:#333,color:#000 | Element | Question it answers | Example |
|---|---|---|
| Principal | Who needs access? | App Service's managed identity |
| Role Definition | What permission? | Key Vault Secrets User, Storage Blob Data Reader |
| Scope | On which resource? | A specific Key Vault, Storage account, or resource group |
| Role Assignment | The binding itself | Unique GUID — one per (principal + role + scope) combination |
Azure RBAC enforces a uniqueness constraint: only one role assignment can exist for the same (principal, role definition, scope) triple. Attempting to create a duplicate with a different assignment GUID results in a RoleAssignmentExists conflict.
Prerequisites¶
- App Service app deployed and running
- Permission to assign RBAC roles on target Azure resources
Azure.Identityand relevant Azure SDK packages installed
Main content¶
1) Enable system-assigned managed identity¶
az webapp identity assign \
--resource-group "$RESOURCE_GROUP_NAME" \
--name "$WEB_APP_NAME" \
--output json
Capture principal ID:
export WEB_APP_PRINCIPAL_ID=$(az webapp identity show \
--resource-group "$RESOURCE_GROUP_NAME" \
--name "$WEB_APP_NAME" \
--query "principalId" \
--output tsv)
2) Grant RBAC on target resource¶
Example: Key Vault secrets reader role assignment.
az role assignment create \
--assignee-object-id "$WEB_APP_PRINCIPAL_ID" \
--assignee-principal-type ServicePrincipal \
--role "Key Vault Secrets User" \
--scope "/subscriptions/<subscription-id>/resourceGroups/<rg-name>/providers/Microsoft.KeyVault/vaults/<vault-name>" \
--output json
3) Add Azure SDK packages¶
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.12.0" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.6.0" />
<PackageReference Include="Azure.Storage.Blobs" Version="12.21.1" />
</ItemGroup>
4) Use DefaultAzureCredential in code¶
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
builder.Services.AddSingleton(_ =>
{
var vaultUri = new Uri("https://<vault-name>.vault.azure.net/");
return new SecretClient(vaultUri, new DefaultAzureCredential());
});
Read secret in endpoint:
[ApiController]
[Route("api/secrets")]
public sealed class SecretsController : ControllerBase
{
private readonly SecretClient _secretClient;
public SecretsController(SecretClient secretClient) => _secretClient = secretClient;
[HttpGet("sample")]
public async Task<IActionResult> GetSampleSecret(CancellationToken cancellationToken)
{
var secret = await _secretClient.GetSecretAsync("sample-secret", cancellationToken: cancellationToken);
return Ok(new { name = secret.Value.Name, length = secret.Value.Value.Length });
}
}
5) Local development behavior¶
DefaultAzureCredential uses local identity chain during development (Azure CLI / VS sign-in) and managed identity in App Service automatically.
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
ExcludeInteractiveBrowserCredential = true
});
6) Code-free auth pattern for Azure clients¶
Many Azure SDK clients only need endpoint + credential:
var blobServiceClient = new BlobServiceClient(new Uri("https://<storage-account>.blob.core.windows.net"), new DefaultAzureCredential());
7) Azure DevOps identity-aware validation snippet¶
- task: AzureCLI@2
displayName: Verify managed identity principal exists
inputs:
azureSubscription: $(azureSubscription)
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az webapp identity show \
--resource-group $(resourceGroupName) \
--name $(webAppName) \
--output table
Least privilege first
Assign only the minimum role at the narrowest scope. Prefer resource-level scope over subscription-level assignments.
Verification¶
Expect successful response without any secret stored in source code or App Settings plain text.
Troubleshooting¶
403 from target resource¶
- Confirm correct role assignment and scope.
- Wait for RBAC propagation (can take several minutes).
- Verify managed identity principal ID used in assignment is current.
Works locally but fails in App Service¶
- Confirm system-assigned identity is enabled on deployed app.
- Confirm outbound networking and DNS allow resource access.
- Remove local-only credential assumptions from code.
Unexpected credential in local development¶
Set explicit exclusions in DefaultAzureCredentialOptions to prevent credential chain surprises.
Run It in the Portal¶
Portal view: Identity blade (canonical managed-identity enablement surface)¶

The Identity blade is the canonical Portal surface for the managed-identity flow this recipe walks through for the ASP.NET Core app. With System assigned active and the Status toggle moved from Off to On, App Service creates the Entra ID principal that the recipe then uses with Azure.Identity DefaultAzureCredential to access downstream resources via Azure.Security.KeyVault.Secrets, Azure.Storage.Blobs, and other Entra-aware SDK clients. The descriptive header on this blade restates the lifecycle guarantee the recipe relies on — the identity is tied to the resource — and the User assigned tab is the alternative path the recipe covers for identities shared across multiple apps. Use this blade as step 1 of the recipe before issuing any downstream role assignments via the CLI.
See Also¶
- Key Vault References
- Azure SQL
- For platform details, see Azure App Service Guide