Azure Key Vault: Securing Your Application Secrets

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:

  1. Creating and configuring Azure Key Vault
  2. Storing secrets, keys, and certificates
  3. Implementing secure access with managed identities
  4. Integrating Key Vault with applications
  5. Monitoring and auditing secret access
  6. Production-ready security patterns

Architecture Overview

flowchart LR App1[App Service / Function] --> KV[(Key Vault)] App2[Container App] --> KV MI1[(Managed Identity)] --- App1 MI2[(Managed Identity)] --- App2 KV --> LA[(Log Analytics)] KV --> EV[Event Grid] EV --> Rotator[Rotation Function] Rotator --> KV Sub[(Subscription Policy)] --> KV Admin[Security Admin] --> KV

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:

  1. Minimize credential lifetime (limit blast radius).
  2. Reduce manual handling risk & human error.
  3. 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:

  1. Detect failure (app telemetry errors after deploy).
  2. Fetch prior secret version ID.
  3. Promote previous version by updating reference (env variable / config store).
  4. 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

Key Vault Overview
Access Model
Private Endpoint

References (Additional)

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:

  1. Store both current and previous secret versions
  2. Update applications to use new secrets
  3. Deprecate old secrets after grace period
  4. 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

Additional Resources


How are you managing secrets in your applications? Have you implemented Key Vault? Share your security journey and questions below!