Azure Key Vault: Securing Your Application Secrets
Introduction
Every application needs secretsβdatabase connection strings, API keys, certificates, encryption keys. The worst-case scenario? Hardcoding these in source code where they're exposed in version control, leaked through logs, or compromised during incidents. Azure Key Vault solves this critical security challenge by providing centralized, encrypted storage with fine-grained access control and comprehensive audit logging.
This matters because secret sprawl represents one of the most common attack vectors in cloud applications. A single exposed API key can grant unauthorized access to entire systems. Key Vault eliminates this risk while simplifying secret rotation, compliance auditing, and disaster recovery. Whether you're building a new microservice or securing legacy applications, Key Vault is essential infrastructure for any serious Azure deployment.
Prerequisites
- Azure subscription (free trial available)
- Azure CLI version 2.40 or later
- Basic understanding of Azure Active Directory (now Microsoft Entra ID)
- An application or service that needs to store secrets (we'll use a simple example)
- Contributor or Key Vault Contributor role on your subscription
Overview
In this guide, you'll create an Azure Key Vault, store various types of secrets, implement secure access patterns using managed identities, and integrate Key Vault with a Node.js application. We'll cover:
- Creating and configuring Azure Key Vault
- Storing secrets, keys, and certificates
- Implementing secure access with managed identities
- Integrating Key Vault with applications
- Monitoring and auditing secret access
- Production-ready security patterns
Architecture Overview
Key points:
- Managed identities eliminate credential storage.
- Event Grid secret expiry notifications trigger rotation workflows.
- Centralized diagnostics stream to Log Analytics for audit & anomaly detection.
- Azure Policy enforces configuration (purge protection, private endpoints).
RBAC vs Access Policies Decision Matrix
| Criteria | RBAC Authorization | Access Policies |
|---|---|---|
| Granularity | High (role definitions, data vs management) | Medium (coarse permissions sets) |
| Governance | Centralized across Azure resources | Per-vault only |
| Automation | Easier with ARM/Bicep & Azure AD groups | Requires explicit policy edits |
| Conditional Access | Supported via Azure AD | Not natively integrated |
| Migration Path | Recommended modern approach | Legacy / backward compatibility |
| Least Privilege | More precise role scoping | Broader permission buckets |
| Auditing Consistency | Unified with other resource access | Separate model |
| Future Enhancements | Actively invested by platform | Limited new features |
Recommendation: Use RBAC unless strict legacy compatibility blocks migration. Maintain a short deprecation plan for remaining access policies.
Secret & Key Rotation Automation
Rotation objectives:
- Minimize credential lifetime (limit blast radius).
- Reduce manual handling risk & human error.
- Provide deterministic rollback path.
| Scenario | Mechanism | Suggested Interval | Notes |
|---|---|---|---|
| API Keys | Azure Function scheduled (Timer trigger) | 30β90 days | Store previous version for grace period |
| DB Passwords | Scripted rotation + app config refresh | 60 days | Coordinate with DB user policy |
| Certificates | Built-in Key Vault certificate auto-renew | Before expiry (per CA) | Use notifications for pre-validation |
| Encryption Keys | Key rotation policy (keys) | 12β24 months | Align with compliance (PCI, ISO) |
| Secrets with Vendor Limits | Manual + monitored | Vendor-defined | Add expiry metadata for alerts |
Event-driven rotation example (Timer + Event Grid dual approach):
# Timer-triggered Azure Function (script skeleton)
# Pseudocode: Rotate secret if within threshold
param($Timer)
$ThresholdDays = 15
$VaultName = $env:KEY_VAULT_NAME
$SecretName = 'ThirdPartyApiKey'
$Secret = az keyvault secret show --vault-name $VaultName --name $SecretName | ConvertFrom-Json
$Expiry = [DateTime]$Secret.attributes.expires
if ((($Expiry - (Get-Date)).TotalDays) -lt $ThresholdDays) {
Write-Host "Rotating $SecretName"
$NewValue = (New-Guid).Guid.Replace('-', '').Substring(0,32)
az keyvault secret set --vault-name $VaultName --name $SecretName --value $NewValue --expires $((Get-Date).AddMonths(12).ToString('s')+'Z') | Out-Null
}
Key rotation via Bicep module (using verified AVM module):
module kv 'br/public:avm/res/key-vault/vault:<version>' = {
name: 'kvEnterprise'
params: {
name: 'kv-enterprise-prod'
enablePurgeProtection: true
enableRbacAuthorization: true
keys: [
{
name: 'encKey'
rotationPolicy: {
attributes: {
expiryTime: 'P2Y'
}
lifetimeActions: [
{
action: { type: 'rotate' }
trigger: { timeBeforeExpiry: 'P30D' }
}
{
action: { type: 'notify' }
trigger: { timeBeforeExpiry: 'P60D' }
}
]
}
}
]
}
}
Performance & Caching Strategies
| Concern | Symptom | Strategy | Notes |
|---|---|---|---|
| Latency | Repeated secret fetch calls | In-memory cache (TTL) | Avoid long TTL > rotation cycle |
| Startup Delay | Cold start needs many secrets | Batch fetch asynchronously | Parallelize when safe |
| Throttling | 429 responses from vault | Exponential backoff + jitter | Monitor metrics for sustained 429s |
| Regional Resilience | Higher latency cross-region | Prefer local vault per region | Sync secrets via pipeline, not app |
| Large Secret Payloads | Increased transfer time | Keep secrets small (config β App Config) | Separate config vs sensitive values |
Example Node.js cache wrapper:
class SecretCache {
constructor(client, ttlMs = 300000) { // 5 min default
this.client = client; this.ttlMs = ttlMs; this.store = new Map();
}
async get(name) {
const entry = this.store.get(name);
const now = Date.now();
if (entry && (now - entry.ts) < this.ttlMs) return entry.value;
const secret = await this.client.getSecret(name);
this.store.set(name, { value: secret.value, ts: now });
return secret.value;
}
}
Observability & Auditing Enhancements
Kusto queries:
AzureDiagnostics
| where ResourceType == 'VAULTS'
| summarize count() by OperationName
AzureDiagnostics
| where OperationName == 'SecretGet'
| summarize requests = count() by identity_claim_upn_s
| order by requests desc
Alert Strategy:
- Spike in SecretGet operations for single identity.
- Unexpected operations (e.g., purge attempts) outside maintenance window.
- Approaching secret expiry (< 14 days) without rotation event.
Resilience & Disaster Recovery
| Risk | Mitigation | Detail |
|---|---|---|
| Accidental deletion | Soft delete + purge protection | Mandatory for production |
| Regional outage | Multiple vaults (active-active) | Deploy per region + pipeline sync |
| Compromised secret | Immediate version rollback | Re-point apps to previous version |
| Access escalation | Principle of least privilege reviews | Quarterly RBAC audit |
| Network isolation failure | Private endpoint + deny public | Policy enforcement |
Rotation rollback pattern:
- Detect failure (app telemetry errors after deploy).
- Fetch prior secret version ID.
- Promote previous version by updating reference (env variable / config store).
- Trigger targeted app restart.
Troubleshooting Matrix (Extended)
| Symptom | Root Cause | Diagnosis | Resolution |
|---|---|---|---|
| High latency (p95 > 200ms) | Excessive per-request secret fetch | App Insights dependency traces | Implement TTL caching layer |
| Frequent 401 on secret reads | Managed identity token failure | Identity logs / AzureDiagnostics | Reassign role; check identity state |
| Rotation script fails silently | Missing Function permissions | Function app role assignments | Grant Secrets Officer to function identity |
| Unauthorized purge attempt | Malicious / misconfigured principal | AuditEvent logs | Review RBAC; disable access; alert SOC |
| Expired certificate not auto-renewed | CA integration misconfigured | Certificate status in portal | Re-import with correct policy |
| Inconsistent secrets across regions | Manual sync oversight | Compare versions via CLI | Automate sync in pipeline |
Image References



References (Additional)
- Key Vault RBAC vs Access Policies
- Key Rotation Concepts
- Secret Expiration & Alerts
- Event Grid Integration
- Azure Policy Samples for Key Vault
Step-by-Step Guide
Step 1: Create an Azure Key Vault
First, set up a resource group and Key Vault:
# Set variables for reusability
$RESOURCE_GROUP="rg-keyvault-demo"
$LOCATION="eastus"
$KEY_VAULT_NAME="kv-secure-app-$((Get-Random -Maximum 9999))"
# Create resource group
az group create --name $RESOURCE_GROUP --location $LOCATION
# Create Key Vault with RBAC authorization (recommended over access policies)
az keyvault create `
--name $KEY_VAULT_NAME `
--resource-group $RESOURCE_GROUP `
--location $LOCATION `
--enable-rbac-authorization true `
--enabled-for-deployment false `
--enabled-for-disk-encryption false `
--enabled-for-template-deployment false
# Get your user object ID
$USER_OBJECT_ID = az ad signed-in-user show --query id -o tsv
# Grant yourself Key Vault Secrets Officer role
az role assignment create `
--role "Key Vault Secrets Officer" `
--assignee $USER_OBJECT_ID `
--scope "/subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.KeyVault/vaults/$KEY_VAULT_NAME"
Important Configuration Choices:
- RBAC Authorization: Modern, recommended approach for access control
- Deployment Flags: Disabled for security (enable only if needed)
- Soft Delete: Enabled by default (retains deleted secrets for 90 days)
- Purge Protection: Consider enabling for production to prevent permanent deletion
Step 2: Store Secrets in Key Vault
Add different types of secrets:
# Store a database connection string
az keyvault secret set `
--vault-name $KEY_VAULT_NAME `
--name "DatabaseConnectionString" `
--value "Server=myserver.database.windows.net;Database=mydb;User Id=admin;Password=SecureP@ssw0rd!"
# Store an API key with expiration
$EXPIRY_DATE = (Get-Date).AddYears(1).ToString("yyyy-MM-ddTHH:mm:ssZ")
az keyvault secret set `
--vault-name $KEY_VAULT_NAME `
--name "ThirdPartyApiKey" `
--value "sk_live_51H..." `
--expires $EXPIRY_DATE
# Store a certificate (upload from file)
az keyvault certificate import `
--vault-name $KEY_VAULT_NAME `
--name "AppServiceCertificate" `
--file "path/to/certificate.pfx" `
--password "cert-password"
# Create a cryptographic key for encryption
az keyvault key create `
--vault-name $KEY_VAULT_NAME `
--name "DataEncryptionKey" `
--kty RSA `
--size 2048 `
--ops encrypt decrypt
Retrieve a secret to verify:
# Get secret value
az keyvault secret show `
--vault-name $KEY_VAULT_NAME `
--name "DatabaseConnectionString" `
--query "value" -o tsv
Step 3: Implement Managed Identity Access
Create a managed identity for secure, password-less authentication:
# Create a user-assigned managed identity
$IDENTITY_NAME="id-secure-app"
az identity create `
--name $IDENTITY_NAME `
--resource-group $RESOURCE_GROUP `
--location $LOCATION
# Get the identity's principal ID
$IDENTITY_PRINCIPAL_ID = az identity show `
--name $IDENTITY_NAME `
--resource-group $RESOURCE_GROUP `
--query principalId -o tsv
# Grant the identity read access to secrets
az role assignment create `
--role "Key Vault Secrets User" `
--assignee $IDENTITY_PRINCIPAL_ID `
--scope "/subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.KeyVault/vaults/$KEY_VAULT_NAME"
For Azure services (App Service, Functions, VMs), enable system-assigned identity:
# Example: Enable managed identity on an App Service
az webapp identity assign `
--name "my-app-service" `
--resource-group $RESOURCE_GROUP
# Then grant it Key Vault access using the same role assignment pattern
Step 4: Integrate Key Vault with Your Application
Here's how to access Key Vault from a Node.js application using managed identity:
Install dependencies:
npm install @azure/keyvault-secrets @azure/identity
Application code:
const { SecretClient } = require("@azure/keyvault-secrets");
const { DefaultAzureCredential } = require("@azure/identity");
async function getSecret(secretName) {
try {
// DefaultAzureCredential automatically uses managed identity in Azure
// and falls back to Azure CLI credentials locally
const credential = new DefaultAzureCredential();
const vaultUrl = `https://${process.env.KEY_VAULT_NAME}.vault.azure.net`;
const client = new SecretClient(vaultUrl, credential);
// Retrieve the secret
const secret = await client.getSecret(secretName);
console.log(`Retrieved secret: ${secretName}`);
return secret.value;
} catch (error) {
console.error(`Error retrieving secret: ${error.message}`);
throw error;
}
}
async function main() {
// Example: Get database connection string
const dbConnectionString = await getSecret("DatabaseConnectionString");
// Use the secret (never log the actual value!)
console.log("Successfully retrieved database connection");
// Example: Get API key
const apiKey = await getSecret("ThirdPartyApiKey");
// Use secrets in your application
// initializeDatabase(dbConnectionString);
// configureApiClient(apiKey);
}
main().catch(error => {
console.error("Application error:", error);
process.exit(1);
});
Set environment variable:
# Locally (for development)
$env:KEY_VAULT_NAME="kv-secure-app-1234"
# In Azure App Service (production)
az webapp config appsettings set `
--name "my-app-service" `
--resource-group $RESOURCE_GROUP `
--settings KEY_VAULT_NAME="kv-secure-app-1234"
Step 5: Enable Monitoring and Auditing
Configure diagnostic settings to track secret access:
# Create a Log Analytics workspace
az monitor log-analytics workspace create `
--workspace-name "law-keyvault-audit" `
--resource-group $RESOURCE_GROUP `
--location $LOCATION
# Get workspace ID
$WORKSPACE_ID = az monitor log-analytics workspace show `
--workspace-name "law-keyvault-audit" `
--resource-group $RESOURCE_GROUP `
--query id -o tsv
# Enable diagnostic logging
az monitor diagnostic-settings create `
--name "KeyVaultAuditLogs" `
--resource "/subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RESOURCE_GROUP/providers/Microsoft.KeyVault/vaults/$KEY_VAULT_NAME" `
--workspace $WORKSPACE_ID `
--logs '[{"category": "AuditEvent", "enabled": true}]' `
--metrics '[{"category": "AllMetrics", "enabled": true}]'
Query audit logs:
// Run in Log Analytics workspace
AzureDiagnostics
| where ResourceType == "VAULTS"
| where OperationName == "SecretGet"
| project TimeGenerated, CallerIPAddress, identity_claim_upn_s, requestUri_s
| order by TimeGenerated desc
Step 6: Implement Secret Rotation
Automate secret rotation using Azure Key Vault rotation policy:
# Set rotation policy for a secret (90-day rotation)
az keyvault secret set-attributes `
--vault-name $KEY_VAULT_NAME `
--name "ThirdPartyApiKey" `
--expires $EXPIRY_DATE
# For automated rotation, use Azure Functions or Logic Apps
# Trigger rotation based on expiry notifications
Best practice pattern:
- Store both current and previous secret versions
- Update applications to use new secrets
- Deprecate old secrets after grace period
- Monitor for applications still using old versions
Best Practices
Use Managed Identities: Never store Key Vault credentials in code or configuration files. Always use managed identities for Azure-hosted applications and service principals with minimal permissions for external services.
Implement Least Privilege: Grant only the specific permissions needed (e.g., "Key Vault Secrets User" for read-only access). Avoid broad roles like "Key Vault Administrator" for application identities.
Enable Soft Delete and Purge Protection: Protect against accidental or malicious deletion. Soft delete retains secrets for 90 days; purge protection prevents permanent deletion during that period.
Separate Key Vaults by Environment: Use different Key Vaults for dev, test, and production. This isolation prevents cross-environment secret leaks and simplifies access management.
Implement Secret Versioning: Key Vault maintains secret versions automatically. Reference specific versions in production; use "latest" cautiously and only after thorough testing.
Monitor and Alert: Configure alerts for unusual access patterns, failed authentication attempts, or secret expirations. Use Azure Monitor and Log Analytics for comprehensive auditing.
Rotate Secrets Regularly: Establish rotation policies (30-90 days for highly sensitive secrets). Automate rotation where possible using Azure Functions or Key Vault rotation features.
Use Network Restrictions: Configure firewall rules and private endpoints to limit Key Vault access to specific networks. Disable public access for production Key Vaults when possible.
Common Issues & Troubleshooting
Issue: The user, group or application does not have secrets get permission
Solution: Verify RBAC role assignments using az role assignment list --scope <key-vault-resource-id>. Ensure the identity has "Key Vault Secrets User" or higher role. Role propagation can take 5-10 minutes after assignment.
Issue: DefaultAzureCredential failed to retrieve a token when running locally
Solution: Ensure you're logged into Azure CLI (az login). For managed identity to work locally, the CLI credentials are used as a fallback. In production, verify the managed identity is enabled and has proper role assignments.
Issue: Secrets not accessible from Azure App Service
Solution: 1) Verify managed identity is enabled on the App Service. 2) Check the identity has appropriate Key Vault role assignments. 3) Ensure firewall rules allow App Service outbound IP addresses if network restrictions are configured. 4) Verify the Key Vault URL is correct in application settings.
Issue: Certificate import fails with parsing error
Solution: Ensure the certificate is in PFX/PKCS12 format with a password. PEM files need conversion first. Verify the certificate chain is complete and the password is correct.
Key Takeaways
- β Azure Key Vault centralizes secret management, eliminating hardcoded credentials and secret sprawl
- β Managed identities provide secure, password-less authentication between Azure services
- β RBAC-based access control offers fine-grained permissions aligned with zero-trust principles
- β Comprehensive audit logging tracks all secret access for compliance and security monitoring
- β Built-in features like soft delete, versioning, and rotation policies protect against common security pitfalls
Next Steps
- Integrate Key Vault with Azure App Configuration for centralized configuration management
- Explore Key Vault Managed HSM for FIPS 140-2 Level 3 compliance requirements
- Implement Azure Policy to enforce Key Vault best practices across subscriptions
- Set up Azure Key Vault rotation with Azure Functions
- Learn about Customer-Managed Keys for encrypting Azure services
Additional Resources
- Microsoft Learn - Azure Key Vault
- Azure Key Vault Documentation
- Key Vault Security Recommendations
- Azure SDK for JavaScript - Key Vault
- Zero Trust Security Model
How are you managing secrets in your applications? Have you implemented Key Vault? Share your security journey and questions below!