Secrets in Azure Key Vault
A secret in Azure Key Vault is any string value you want to protect — database passwords, API tokens, connection strings, signing keys. Key Vault stores them encrypted, versions them automatically, and gives you fine-grained control over who can read them and when they expire. The most useful capability for application developers is the Key Vault reference feature, which lets App Service and Functions read secrets from Key Vault without writing a single line of SDK code.
Creating and updating secrets
A secret in Key Vault has a name, a value, and optional metadata including content type, enabled status, activation date, and expiry date. The name must consist of alphanumeric characters and hyphens.
# Create a new secret
az keyvault secret set \
--vault-name my-vault \
--name "postgres-password" \
--value "correct-horse-battery-staple"
# Create a secret with an expiry date (ISO 8601 format)
az keyvault secret set \
--vault-name my-vault \
--name "stripe-api-key" \
--value "sk_live_abc123" \
--expires "2026-12-31T00:00:00Z" \
--content-type "application/x-api-key"
# Update a secret (creates a new version, does not overwrite)
az keyvault secret set \
--vault-name my-vault \
--name "postgres-password" \
--value "new-secure-password-here"When you run az keyvault secret set a second time with the same name, Key Vault creates a new version of the secret. The old version is preserved and can still be read by its specific version ID. This versioning is automatic — you do not need to do anything special to enable it.
Reading secrets
There are three ways to read a secret: the CLI, the SDK, or an HTTP REST call. All three require that the caller has been granted the Key Vault Secrets User role (or equivalent access policy permissions) on the vault or the specific secret.
# Read the latest version of a secret
az keyvault secret show \
--vault-name my-vault \
--name "postgres-password" \
--query value \
--output tsv
# List all secrets in a vault (shows names and metadata, not values)
az keyvault secret list \
--vault-name my-vault \
--output table
# List versions of a specific secret
az keyvault secret list-versions \
--vault-name my-vault \
--name "postgres-password" \
--output table
# Read a specific version
az keyvault secret show \
--vault-name my-vault \
--name "postgres-password" \
--version "abc123def456..."In Python using the Azure SDK:
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
credential = DefaultAzureCredential()
client = SecretClient(
vault_url="https://my-vault.vault.azure.net/",
credential=credential
)
# Get the latest version
secret = client.get_secret("postgres-password")
print(secret.value)
# Get a specific version
secret = client.get_secret("postgres-password", version="abc123def456...")Secret versioning and expiry dates
Every update to a secret creates a new version with a unique version ID. The version ID is a 32-character hex string. When you retrieve a secret by name without specifying a version, you always get the current (latest) version.
The previous version remains accessible by its specific version ID until you explicitly disable or delete it. This means you can always look up what value a secret had at any point in time, which is valuable for incident investigations (“what was the database password at 3:47 AM last Tuesday?”).
Expiry dates (—expires) tell Key Vault to stop serving a secret after a certain date. The secret is not deleted; it becomes inaccessible to normal retrieval calls. If you try to read an expired secret, you get a 403 error. This is a useful control for time-limited credentials (OAuth tokens, temporary access keys) where you want to be certain the credential cannot be used after a certain date even if the consumer forgets to stop using it.
Expiry on secrets does not trigger automatic rotation — the secret just becomes unreadable. If your application reads an expired secret, it fails. Expiry is most useful when combined with alerting (via Key Vault near-expiry events) so you have time to rotate before the deadline. See Rotating Secrets Automatically for how to build automated rotation around this.
Key Vault references in App Service and Functions
This is the feature that significantly changes how most teams handle secrets in Azure web applications. Instead of writing code to call Key Vault and read a secret value at startup, you put a special reference string in your App Service or Azure Functions application settings. The platform reads the secret from Key Vault on your behalf and injects the plain value into your app as an environment variable. Your application code reads it the same way it reads any other environment variable.
The syntax
There are two forms of the reference syntax:
# Reference using the secret URI (no version — always reads latest)
@Microsoft.KeyVault(SecretUri=https://my-vault.vault.azure.net/secrets/postgres-password/)
# Reference using a specific version
@Microsoft.KeyVault(SecretUri=https://my-vault.vault.azure.net/secrets/postgres-password/abc123def456...)
# Alternative form using vault name and secret name separately
@Microsoft.KeyVault(VaultName=my-vault;SecretName=postgres-password;SecretVersion=abc123def456...)The first form (no version) means your app always reads the latest version of the secret. When you rotate a secret by creating a new version in Key Vault, your app automatically picks up the new value without any redeployment or configuration change. This is the form you want in most cases.
Setting it up
The app needs a system-assigned (or user-assigned) managed identity, and that identity needs Key Vault Secrets User access on the vault.
# Step 1: Enable managed identity on the App Service
az webapp identity assign \
--name my-web-app \
--resource-group my-rg
# Step 2: Get the managed identity's principal ID
PRINCIPAL_ID=$(az webapp identity show \
--name my-web-app \
--resource-group my-rg \
--query principalId \
--output tsv)
# Step 3: Get the vault's resource ID
VAULT_ID=$(az keyvault show \
--name my-vault \
--resource-group my-rg \
--query id \
--output tsv)
# Step 4: Grant the identity permission to read secrets
az role assignment create \
--assignee $PRINCIPAL_ID \
--role "Key Vault Secrets User" \
--scope $VAULT_ID
# Step 5: Set the app setting with a Key Vault reference
az webapp config appsettings set \
--name my-web-app \
--resource-group my-rg \
--settings "DATABASE_URL=@Microsoft.KeyVault(SecretUri=https://my-vault.vault.azure.net/secrets/postgres-password/)"After this, your application code simply reads the DATABASE_URL environment variable. It receives the plaintext value from Key Vault — it has no idea the value came from a vault rather than a direct setting.
import os
db_url = os.environ["DATABASE_URL"] # Contains the actual secret valueVerifying the reference resolved
In the Azure Portal, go to your App Service, then Configuration → Application Settings. The Value column will show either the resolved value or an error indicator. If you see @Microsoft.KeyVault(…) still displayed as the raw string, it means the reference did not resolve — usually because the managed identity lacks permission, or the vault name or secret name is wrong.
Key Vault references work in App Service (Web Apps and API Apps) and Azure Functions. They do not work in Azure Container Apps or AKS directly — those platforms require you to use the CSI secrets store driver or SDK calls instead.
The anti-pattern: reading secrets at deploy time
A common mistake is to read secrets from Key Vault during a deployment pipeline and inject them as plain environment variables or configuration values. This pattern looks like this: a CI/CD pipeline authenticates to Key Vault, retrieves the secrets, and sets them as environment variables on the target service during the deployment step.
The problem is that the secret value is now stored as a plain environment variable — outside Key Vault — and was visible in the deployment pipeline as a variable (likely in logs, depending on how the pipeline is configured). The rotation story also breaks: if you rotate the secret in Key Vault, the application still has the old value baked into its environment variables and you must redeploy to pick up the new value.
The right approach is runtime resolution: the application (or the platform, via Key Vault references) reads the secret from Key Vault when it starts, not when it deploys. The secret is never baked into a deployment artifact.
| Approach | Secret exposed in pipeline? | Rotation requires redeploy? | Recommended? |
|---|---|---|---|
| Read from Key Vault at deploy time → inject as env var | Yes (pipeline variable) | Yes | No |
| Key Vault reference (App Service / Functions) | No | No (auto-updates) | Yes |
| SDK reads from Key Vault at runtime | No | Depends on caching strategy | Yes |
Organising secrets in Key Vault
Key Vault does not support folders or hierarchical namespaces. All secrets in a vault are at the same level, identified only by name. Teams that store many secrets often use naming conventions like appname-environment-secretname (e.g., payments-prod-stripe-key) to provide logical grouping that makes az keyvault secret list output readable.
Consider creating separate vaults per environment (development, staging, production) rather than using a naming convention to separate them in one vault. Separate vaults mean separate access control, separate audit logs, and no risk of a developer reading a production secret when they meant to read the staging one. This also aligns with the principle of least privilege — developers can have broad access to the development vault and narrow access (or no access) to the production vault.
Common mistakes
- Hardcoding the secret version in a Key Vault reference. If you pin to a specific version (
SecretVersion=abc123…), your app will keep reading that version after you rotate the secret. Use the versionless URI so your app automatically gets the latest value after rotation. - Granting Secrets Officer instead of Secrets User to the app’s managed identity. Secrets User can read secrets. Secrets Officer can create, update, and delete them. Your web application only needs to read secrets — grant the minimum required role.
- Not caching secrets in SDK code. If your application calls Key Vault on every HTTP request to read a secret, you will generate a very large number of Key Vault API calls and your application performance will suffer. Read secrets once at startup (or use a short in-memory cache) and refresh them periodically or on a cache-miss.
- Storing secrets in one vault with mixed permissions. If two applications share a vault but should have access to different secrets, access policies cannot help you (they operate at vault level). Use RBAC with per-secret role assignments, or use separate vaults per application.
Summary
- Secrets in Key Vault are versioned automatically — every update creates a new version. Previous versions remain accessible by their version ID.
- Key Vault references (
@Microsoft.KeyVault(…)) let App Service and Azure Functions read secrets without writing any SDK code. The platform injects the resolved value as a plain environment variable. - Use the versionless Key Vault reference URI so that your app automatically picks up new secret values after rotation, without redeployment.
- Never read secrets at deploy time and inject them as environment variables — use runtime resolution via Key Vault references or SDK calls instead.
- Use separate vaults per environment for separate access control and cleaner audit logs.
Frequently asked questions
What is a Key Vault reference and how is it different from reading a secret in code?
A Key Vault reference is a special syntax you put in an App Service or Azure Functions application setting. The platform itself reads the secret from Key Vault and injects the value into your app as a plain environment variable — your code never calls Key Vault directly. The syntax is @Microsoft.KeyVault(SecretUri=https://vault-name.vault.azure.net/secrets/secret-name/) and it requires the app to have a system-assigned managed identity with Key Vault Secrets User access.
How does secret versioning work in Key Vault?
Every time you update a secret in Key Vault, a new version is created with a unique version ID. The previous version is not deleted; it is marked as the non-current version but remains readable if you reference it by its specific version ID. If you reference a secret without a version (using just the secret name), you always get the latest version. This allows you to roll back to a previous version simply by referencing its specific version ID.
Can I set an expiry date on a Key Vault secret?
Yes. When creating or updating a secret, you can set not-before and expires attributes. After the expiry date, the secret is still present but cannot be retrieved via the normal get operations — attempts return a 403 Forbidden error. The secret is not automatically deleted. You need to create a new version with a new expiry date to restore access.