Introduction
Azure Key Vault uses either Access Policies or Azure RBAC to control access to secrets. A 403 Forbidden error from the SecretClient means the authenticated identity does not have the required permissions. This is a common issue when deploying to Azure App Service, AKS, or Azure Functions where managed identity setup is complex and permissions must be explicitly granted.
Symptoms
Azure.RequestFailedException: Status 403 (Forbidden)Access deniedwhen callingclient.GetSecretAsync()- Works locally with Azure CLI credentials but fails in Azure
DefaultAzureCredentialauthenticates but Key Vault rejects- Error:
The user, group or application does not have secrets get permission
Example error: ``` Azure.RequestFailedException: Forbidden Status: 403 (Forbidden) ErrorCode: Forbidden
Content: {"error":{"code":"Forbidden","message":"Access denied.\r\nCaller is not authorized to perform action on resource.\r\nIf role assignments, deny assignments or role definitions were changed recently, please observe propagation time."}} ```
Common Causes
- Managed identity not enabled on the Azure resource
- Access policy or RBAC role not assigned to the identity
- Wrong Key Vault permission model (Access Policy vs Azure RBAC)
- Managed identity object ID changed after recreation
- Tenant ID mismatch in
DefaultAzureCredential
Step-by-Step Fix
- 1.Verify the identity being used:
- 2.```csharp
- 3.// Add this to diagnose which credential is being used
- 4.var credential = new DefaultAzureCredential(
- 5.new DefaultAzureCredentialOptions
- 6.{
- 7.Diagnostics =
- 8.{
- 9.IsAccountIdentifierLoggingEnabled = true,
- 10.IsLoggingContentEnabled = true
- 11.}
- 12.});
var token = await credential.GetTokenAsync( new TokenRequestContext(new[] { "https://vault.azure.net/.default" }));
Console.WriteLine($"Using token from: {token}"); Console.WriteLine($"Token expires: {token.ExpiresOn}"); ```
- 1.Grant access via Azure CLI:
- 2.```bash
- 3.# Get the managed identity principal ID
- 4.az webapp identity show --resource-group MyResourceGroup --name MyAppName
- 5.# Output: { "principalId": "abc123-def456-..." }
# Grant Key Vault Secrets User role (RBAC model) az role assignment create \ --role "Key Vault Secrets User" \ --assignee "abc123-def456-..." \ --scope "/subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.KeyVault/vaults/{vault-name}"
# Or for Access Policy model: az keyvault set-policy \ --name my-keyvault \ --object-id "abc123-def456-..." \ --secret-permissions get list ```
- 1.Configure DefaultAzureCredential correctly:
- 2.```csharp
- 3.var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
- 4.{
- 5.// Specify the managed identity client ID if using user-assigned identity
- 6.ManagedIdentityClientId = builder.Configuration["Azure:ManagedIdentityClientId"],
// Specify the tenant ID VisualStudioTenantId = builder.Configuration["Azure:TenantId"], VisualStudioCodeTenantId = builder.Configuration["Azure:TenantId"],
// Exclude credential types you do not use ExcludeEnvironmentCredential = true, ExcludeWorkloadIdentityCredential = true, ExcludeInteractiveBrowserCredential = true, });
var client = new SecretClient( new Uri($"https://{vaultName}.vault.azure.net/"), credential);
var secret = await client.GetSecretAsync("MySecretName"); ```
- 1.Use Azure Key Vault configuration provider:
- 2.```csharp
- 3.builder.Configuration.AddAzureKeyVault(
- 4.new Uri($"https://{vaultName}.vault.azure.net/"),
- 5.new DefaultAzureCredential());
// Access secrets like regular configuration var connectionString = builder.Configuration["DatabaseConnectionString"]; ```
Prevention
- Use Azure RBAC (
Key Vault Secrets User) instead of Access Policies for new vaults - Add the identity assignment to your Infrastructure as Code (Bicep/Terraform)
- Test locally with
az loginbefore deploying - Use
az keyvault showto verify the permission model in use - Add startup health check that verifies Key Vault access
- Monitor 403 errors in Azure Monitor for early detection of permission issues