The last part of the series about Azure Key Vault. In part 1 we integrated Key Vault with IConfiguration, and in part 2 we covered rotation and certificates. Here we look at access control and integration with App Service.
Granular access through RBAC
With RBAC enabled on Key Vault, you can control access at the individual secret level, not just at the vault level. This allows the principle of least privilege: an application that only needs Stripe--ApiKey does not have to have access to all secrets in the vault.
Built-in roles for Key Vault
| Role | Permissions | Use case |
|---|---|---|
| Key Vault Administrator | Full control | Management operations, not for applications |
| Key Vault Secrets Officer | CRUD secrets | CI/CD pipelines that rotate secrets |
| Key Vault Secrets User | Read secrets | Applications that consume secrets |
| Key Vault Certificates Officer | CRUD certificates | Certificate management services |
| Key Vault Crypto Officer | CRUD cryptographic keys | Encryption services |
| Key Vault Reader | Read metadata | Monitoring, audit |
Assigning role at individual secret level
KV_ID=$(az keyvault show \
--name my-app-keyvault \
--resource-group my-rg \
--query id -o tsv)
PRINCIPAL_ID=$(az webapp identity show \
--name my-app-service \
--resource-group my-rg \
--query principalId -o tsv)
# Access to ALL secrets in the vault
az role assignment create \
--assignee $PRINCIPAL_ID \
--role "Key Vault Secrets User" \
--scope $KV_ID
# Access ONLY to the secret Stripe--ApiKey
az role assignment create \
--assignee $PRINCIPAL_ID \
--role "Key Vault Secrets User" \
--scope "$KV_ID/secrets/Stripe--ApiKey"
The granular scope (/secrets/SecretName) means the application can read exactly the secrets it needs and nothing else. If another application needs SendGrid--ApiKey, it gets separate access, exactly to that secret.
Complete audit trail
# Enable diagnostic logs for Key Vault
az monitor diagnostic-settings create \
--name "keyvault-audit" \
--resource $KV_ID \
--logs '[{"category": "AuditEvent", "enabled": true}]' \
--workspace "/subscriptions/.../workspaces/my-workspace"
// Query in Log Analytics: who accessed which secret
AzureDiagnostics
| where ResourceType == "VAULTS"
| where OperationName == "SecretGet"
| project TimeGenerated, CallerIPAddress, identity_claim_oid_g, id_s
| order by TimeGenerated desc
With audit logging enabled, every access to a secret is recorded: which identity accessed it, which secret, at what time, from which IP. Exactly what was missing with environment variables.
Key Vault References in App Service
Key Vault References is an App Service feature that allows you to reference a secret from Key Vault directly in Application Settings, without any code changes. App Service resolves the reference at runtime and injects the real value as an environment variable.
Syntax for a Key Vault reference in Application Settings:
@Microsoft.KeyVault(SecretUri=https://my-app-keyvault.vault.azure.net/secrets/Stripe--ApiKey/)
Configuration via Azure CLI
az webapp config appsettings set \
--name my-app-service \
--resource-group my-rg \
--settings \
"Stripe__ApiKey=@Microsoft.KeyVault(SecretUri=https://my-app-keyvault.vault.azure.net/secrets/Stripe--ApiKey/)" \
"SendGrid__ApiKey=@Microsoft.KeyVault(SecretUri=https://my-app-keyvault.vault.azure.net/secrets/SendGrid--ApiKey/)"
App Service resolves the reference automatically. Your application reads configuration["Stripe:ApiKey"] and gets the real value — not the reference. If you rotate the secret in Key Vault (and omit the version from the URI), App Service automatically picks up the new version.
References vs. AddAzureKeyVault
| Criterion | Key Vault References | AddAzureKeyVault |
|---|---|---|
| Code changes | Zero — transparent | Provider in Program.cs |
| Platform support | App Service, Functions, Container Apps | Any .NET application |
| Prefix filtering | No | Yes, via custom manager |
| Reload without restart | Automatic on rotation | Yes, with ReloadInterval |
| RBAC granularity | At secret level | At vault or secret level |
Practical recommendation: use Key Vault References for secrets injected as simple environment variables, and AddAzureKeyVault when you need prefix filtering, granular reload control, or when the application does not run on App Service.
Complete pattern: configuration without any hardcoded secret
Combining Managed Identity with Key Vault, the complete application configuration looks like this:
// appsettings.json -- zero secrets, only structure and non-sensitive values
{
"Logging": { "LogLevel": { "Default": "Information" } },
"KeyVaultUri": "https://my-app-keyvault.vault.azure.net/",
"CosmosDb": {
"Endpoint": "https://my-cosmos.documents.azure.com:443/",
"DatabaseName": "MyAppDb"
},
"Storage": {
"ServiceUri": "https://mystorage.blob.core.windows.net"
}
// Stripe:ApiKey and SendGrid:ApiKey come from Key Vault
}
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsProduction())
{
var kvUri = builder.Configuration["KeyVaultUri"]!;
builder.Configuration.AddAzureKeyVault(
new Uri(kvUri),
new DefaultAzureCredential(),
new AzureKeyVaultConfigurationOptions
{
ReloadInterval = TimeSpan.FromMinutes(30)
});
}
var credential = new DefaultAzureCredential();
// Cosmos DB and Blob Storage -- no secrets (Managed Identity)
builder.Services.AddSingleton(_ => new CosmosClient(
builder.Configuration["CosmosDb:Endpoint"]!, credential));
builder.Services.AddSingleton(_ => new BlobServiceClient(
new Uri(builder.Configuration["Storage:ServiceUri"]!), credential));
// Stripe and SendGrid -- secrets from Key Vault, validated at startup
builder.Services.AddOptions()
.BindConfiguration("Stripe").ValidateDataAnnotations().ValidateOnStart();
builder.Services.AddOptions()
.BindConfiguration("SendGrid").ValidateDataAnnotations().ValidateOnStart();
var app = builder.Build();
app.MapControllers();
await app.RunAsync();
Azure Key Vault Checklist
- Key Vault created with RBAC enabled —
--enable-rbac-authorization true, not Access Policies - Secrets have expiration dates — and notifications configured before expiration
- ReloadInterval configured — for rotation without application restart
- IOptionsSnapshot or IOptionsMonitor — instead of IOptions when you need updated values
- Granular RBAC — each application has access only to the secrets it needs
- Audit logging enabled — all secret accesses recorded in Log Analytics
- Key Vault References — for Application Settings in App Service, without code changes
- Certificates with AutoRenew — automatic renewal 30 days before expiration
If you have questions or want to discuss how to structure your Key Vault for your project, write to me at contact@ludoprogramming.com.