Skip to content

Azure Key Vault & App Configuration Plugin

Our Azure plugin loads secrets from Azure Key Vault and settings from Azure App Configuration using declarative instructions within your .env files.

Both services share one @initAzure() instance and one authentication chain: Managed Identity for Azure-hosted applications, Azure CLI credentials for local development, and service principal or OIDC federated credentials for non-Azure environments. App Configuration can also be read with an access-key connection string.

  • Zero-config authentication - Just provide your vault URL, authentication happens automatically
  • Managed Identity support - No credentials needed for Azure-hosted apps (App Service, Container Instances, VMs, Functions, AKS)
  • Azure CLI authentication - Works with az login for local development
  • Auto-infer secret names from environment variable names (e.g., DATABASE_URL → database-url)
  • Support for service principal credentials (for non-Azure environments)
  • Support for versioned secrets
  • App Configuration settings - Read single settings with azureAppConfig() or load many at once with azureAppConfigBulk(), with label support
  • Key Vault references stored in App Configuration are dereferenced automatically
  • Automatic token caching and renewal
  • OIDC workload identity - Authenticate using platform OIDC tokens (Vercel, GitHub Actions, etc.)
  • Lightweight implementation using the REST APIs (no Azure SDK dependencies)

For global cache mode behavior and CLI cache controls, see the Caching guide.

In a JS/TS project, you may install the @varlock/azure-key-vault-plugin package as a normal dependency. Otherwise you can just load it directly from your .env.schema file, as long as you add a version specifier. See the plugins guide for more instructions on installing plugins.

.env.schema
# 1. Load the plugin
# @plugin(@varlock/azure-key-vault-plugin)
#
# 2. Initialize the plugin - see below for more details on options
# @initAzure(vaultUrl="https://my-vault.vault.azure.net/")

To read from App Configuration instead of (or in addition to) Key Vault, pass appConfigEndpoint. At least one of vaultUrl, appConfigEndpoint, or appConfigConnectionString is required.

.env.schema
# @plugin(@varlock/azure-key-vault-plugin)
# @initAzure(
# vaultUrl="https://my-vault.vault.azure.net/",
# appConfigEndpoint="https://my-store.azconfig.io"
# )

The plugin tries authentication methods in this priority order:

  1. Service Principal - If all three credentials (tenantId, clientId, clientSecret) are provided
  2. OIDC federated credentials - If tenantId and clientId are provided without clientSecret, uses OIDC workload identity federation
  3. Managed Identity - Automatically used when running on Azure infrastructure
  4. Azure CLI - Falls back to az login for local development

The same chain is used for both services. Only the token scope differs (https://vault.azure.net for Key Vault, https://azconfig.io for App Configuration), and tokens are cached per scope. App Configuration additionally accepts an access-key connection string via appConfigConnectionString, which bypasses this chain for App Configuration requests only (see Access-key connection string below).

For most use cases, you only need to provide the vault URL:

.env.schema
# @plugin(@varlock/azure-key-vault-plugin)
# @initAzure(vaultUrl="https://my-vault.vault.azure.net/")

How this works:

  • Local development: Run az login → automatically uses Azure CLI credentials
  • Azure-hosted apps (App Service, Container Instances, VMs, Functions, AKS): Enable Managed Identity → automatically authenticates (no secrets needed!)
  • Works everywhere with zero configuration beyond the vault URL!

Service principal credentials (For non-Azure environments)

Section titled “Service principal credentials (For non-Azure environments)”

If you’re deploying outside of Azure (e.g., AWS, GCP, on-premises), wire up service principal credentials:

  1. Create a service principal with the necessary permissions (see Azure Setup section below)

  2. Wire up the credentials in your config. Add config items for the tenant ID, client ID, and client secret, and reference them when initializing the plugin.

    .env.schema
    # @plugin(@varlock/azure-key-vault-plugin)
    # @initAzure(
    # vaultUrl="https://my-vault.vault.azure.net/",
    # tenantId=$AZURE_TENANT_ID,
    # clientId=$AZURE_CLIENT_ID,
    # clientSecret=$AZURE_CLIENT_SECRET
    # )
    # ---
    # @type=azureTenantId @sensitive
    AZURE_TENANT_ID=
    # @type=azureClientId @sensitive
    AZURE_CLIENT_ID=
    # @type=azureClientSecret @sensitive @internal
    AZURE_CLIENT_SECRET=
  3. Set your credentials in deployed environments. Use your platform’s env var management UI to securely inject these values.

For deployments on platforms that issue OIDC tokens (Vercel, GitHub Actions, GitLab CI, Fly.io, GCP), you can use OIDC workload identity federation to authenticate without a client secret. Varlock auto-detects the platform’s OIDC token and exchanges it for an Azure access token via federated credential authentication.

Provider-side setup:

  1. Create an App Registration (or use an existing one)
  2. Add a federated credential:
Terminal window
az ad app federated-credential create \
--id <app-object-id> \
--parameters '{
"name": "vercel-oidc",
"issuer": "https://oidc.vercel.com",
"subject": "<your-vercel-subject-claim>",
"audiences": ["api://AzureADTokenExchange"]
}'
  1. Grant the App Registration “Key Vault Secrets User” role on your vault.

Varlock config:

.env.schema
# @plugin(@varlock/azure-key-vault-plugin)
# @initAzure(
# vaultUrl="https://my-vault.vault.azure.net/",
# tenantId=$AZURE_TENANT_ID,
# clientId=$AZURE_CLIENT_ID
# )

When clientSecret is omitted but tenantId and clientId are provided, the plugin automatically uses OIDC federated credentials. For platforms not auto-detected, pass an explicit token via oidcToken.

If you need to connect to multiple vaults, but never at the same time, you can alter the vault URL using a function:

.env.schema
# @plugin(@varlock/azure-key-vault-plugin)
# @initAzure(vaultUrl="https://my-vault-${ENV}.vault.azure.net/")

Or if you need to connect to multiple vaults simultaneously, register multiple named instances:

.env.schema
# @initAzure(id=prod, vaultUrl="https://my-vault-prod.vault.azure.net/")
# @initAzure(id=dev, vaultUrl="https://my-vault-dev.vault.azure.net/")
# ---
PROD_SECRET=azureSecret(prod, "database-url")
DEV_SECRET=azureSecret(dev, "database-url")

The same applies to App Configuration stores: give each instance its own appConfigEndpoint and select it with azureAppConfig(id, "key").

Once the plugin is installed and initialized, you can start adding config items that load values using the azureSecret() resolver function.

The azureSecret() function fetches secrets from Azure Key Vault.

.env.schema
# Auto-infer secret names (DATABASE_URL -> "database-url")
DATABASE_URL=azureSecret()
API_KEY=azureSecret()
# Explicit secret names
CUSTOM_SECRET=azureSecret("my-custom-secret-name")

You can fetch specific versions of secrets by appending @version to the secret name:

.env.schema
# Fetch latest version (default)
API_KEY=azureSecret("api-key")
# Fetch specific version
API_KEY_V1=azureSecret("api-key@abc123def456")

Azure App Configuration is a central store for key-value settings. It is the Azure counterpart of AWS Parameter Store: settings are plain values (or JSON), organized by key and optional label, and secrets are usually stored as references to Key Vault rather than in the store itself.

Point the instance at your store with appConfigEndpoint. For Azure Government or Azure China, also set cloud=usgov or cloud=china so tokens are minted for the right endpoints. Authentication uses the same chain as Key Vault, and the identity needs the App Configuration Data Reader role on the store (see Azure Setup below).

.env.schema
# @plugin(@varlock/azure-key-vault-plugin)
# @initAzure(appConfigEndpoint="https://my-store.azconfig.io")

Use defaultLabel to select a label for every lookup that does not name one explicitly. This is the usual way to keep one schema across environments:

.env.schema
# @initAzure(appConfigEndpoint="https://my-store.azconfig.io", defaultLabel="${APP_ENV}")

If you cannot use Entra ID authentication for the store, pass an access-key connection string instead of appConfigEndpoint. The plugin signs each request with HMAC-SHA256 using the key in the connection string. This only authenticates App Configuration requests; Key Vault (including Key Vault references) still goes through the Entra chain.

.env.schema
# @plugin(@varlock/azure-key-vault-plugin)
# @initAzure(appConfigConnectionString=$AZURE_APPCONFIG_CONNECTION_STRING)
# ---
# @type=azureAppConfigConnectionString
AZURE_APPCONFIG_CONNECTION_STRING=

Get a connection string with az appconfig credential list --name my-store --query "[?name=='Primary Read Only'].connectionString" -o tsv. Prefer the read-only key.

The azureAppConfig() function reads one setting. With no arguments it uses the config item key verbatim (App Configuration allows underscores, so no case conversion happens). Pass label= to override defaultLabel for one item; with no label configured, the unlabeled setting is read.

.env.schema
# Reads the setting named "DATABASE_URL" (unlabeled, or defaultLabel if set)
DATABASE_URL=azureAppConfig()
# Explicit key, using App Configuration's usual ":" hierarchy
API_URL=azureAppConfig("services:api:url")
# Explicit label
API_URL_PROD=azureAppConfig("services:api:url", label=production)
# Force the unlabeled setting even when defaultLabel is set
API_URL_BASE=azureAppConfig("services:api:url", label="")
# From a specific instance
API_URL_STAGING=azureAppConfig(staging, "services:api:url")

azureAppConfigBulk() returns every matching setting as a JSON object, for use with @setValuesBulk(). Filter by key (* is a wildcard) and label, and strip a common prefix so the remaining part of each key matches your config item names. Settings whose keys collide after trimming produce an error.

.env.schema
# @plugin(@varlock/azure-key-vault-plugin)
# @initAzure(appConfigEndpoint="https://my-store.azconfig.io")
# @setValuesBulk(azureAppConfigBulk(keyFilter="myapp:*", labelFilter=production, trimKeyPrefix="myapp:"), format=json)
# ---
# Populated from "myapp:DATABASE_URL" and "myapp:API_HOST" with the "production" label
DATABASE_URL=
API_HOST=

keyFilter defaults to *. labelFilter defaults to defaultLabel if set, otherwise to unlabeled settings only. Results are paged through automatically.

App Configuration can store a pointer to a Key Vault secret instead of the value itself (content type application/vnd.microsoft.appconfig.keyvaultref+json). Both azureAppConfig() and azureAppConfigBulk() detect these and fetch the referenced secret, using the instance’s Key Vault credentials. The identity needs “Key Vault Secrets User” (or the “Get” access policy) on each referenced vault.

The reference URI is data written by whoever can edit the store, so the plugin only follows it to a vault it can trust: an https://<vault>.vault.azure.net host (or the equivalent suffix for the selected cloud), or the exact vaultUrl configured on the instance. Any other origin is rejected with an error and never receives the vault token. Because of this, vaultUrl does not need to be set for references within the same cloud.

.env.schema
# In App Configuration, "DB_PASSWORD" is a Key Vault reference to
# https://my-vault.vault.azure.net/secrets/db-password
# @sensitive
DB_PASSWORD=azureAppConfig()

Feature flags are stored as JSON settings (content type application/vnd.microsoft.appconfig.ff+json) under keys prefixed with .appconfig.featureflag/. They are returned as their JSON string; the plugin does not evaluate them.

.env.schema
# Resolves to the flag's JSON, e.g. {"id":"beta","enabled":true,...}
BETA_FLAG=azureAppConfig(".appconfig.featureflag/beta")

Your managed identity, service principal, or user needs one of:

  • Access Policy: “Get” permission for secrets
  • RBAC: “Key Vault Secrets User” role

To read from App Configuration with Entra ID authentication, the identity (managed identity, service principal, or your az login user) needs the App Configuration Data Reader role on the store. Access keys (connection strings) do not need a role assignment.

Terminal window
STORE_ID=$(az appconfig show --name my-store --query id -o tsv)
az role assignment create \
--role "App Configuration Data Reader" \
--assignee <principal-id-or-appId> \
--scope $STORE_ID

If the store holds Key Vault references, the same identity also needs “Key Vault Secrets User” on each referenced vault.

Section titled “Managed Identity for Azure-hosted apps (Recommended)”

Managed Identity is the Azure-native way to authenticate - no credentials needed!

  1. Enable system-assigned managed identity for your Azure resource

    Terminal window
    # For App Service
    az webapp identity assign --name my-app --resource-group my-rg
    # For Container Instance
    az container create --assign-identity --name my-container ...
    # For VM
    az vm identity assign --name my-vm --resource-group my-rg
  2. Grant Key Vault access to the identity

    Get the identity’s principal ID:

    Terminal window
    PRINCIPAL_ID=$(az webapp identity show --name my-app --resource-group my-rg --query principalId -o tsv)

    Then grant access using either RBAC or Access Policy:

    Option A: RBAC (Recommended)

    Terminal window
    az role assignment create \
    --role "Key Vault Secrets User" \
    --assignee $PRINCIPAL_ID \
    --scope /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault-name>

    Option B: Access Policy

    Terminal window
    az keyvault set-policy \
    --name my-vault \
    --object-id $PRINCIPAL_ID \
    --secret-permissions get
  3. That’s it! Your app will automatically authenticate using Managed Identity.

Service principal for non-Azure environments

Section titled “Service principal for non-Azure environments”
  1. Create a service principal

    Terminal window
    az ad sp create-for-rbac --name "varlock-keyvault-reader"

    Save the appId, password, and tenant from the output.

  2. Grant Key Vault access

    Option A: RBAC (Recommended)

    Terminal window
    az role assignment create \
    --role "Key Vault Secrets User" \
    --assignee <appId> \
    --scope /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault-name>

    Option B: Access Policy

    Terminal window
    az keyvault set-policy \
    --name my-vault \
    --spn <appId> \
    --secret-permissions get
  1. Install the Azure CLI if you haven’t already: Installation guide

  2. Log in to Azure

    Terminal window
    az login
  3. Verify your identity

    Terminal window
    az account show
  4. Grant Key Vault access to your user account (if needed)

    Terminal window
    az keyvault set-policy \
    --name my-vault \
    --upn your-email@domain.com \
    --secret-permissions get

Initialize an Azure plugin instance for Key Vault and/or App Configuration. At least one of vaultUrl, appConfigEndpoint, or appConfigConnectionString is required.

Key/value args:

  • vaultUrl (optional): Azure Key Vault URL (e.g., https://my-vault.vault.azure.net/). Required for azureSecret().
  • appConfigEndpoint (optional): Azure App Configuration store endpoint (e.g., https://my-store.azconfig.io). Required for azureAppConfig() / azureAppConfigBulk() unless appConfigConnectionString is set.
  • appConfigConnectionString (optional): App Configuration access-key connection string (Endpoint=...;Id=...;Secret=...). Alternative to appConfigEndpoint plus Entra ID auth; applies to App Configuration requests only.
  • defaultLabel (optional): label used by azureAppConfig() and azureAppConfigBulk() when none is given
  • cloud (optional): public (default), usgov, or china. Selects the Entra authority host, the token audiences for Key Vault and App Configuration, and the Key Vault DNS suffix trusted for Key Vault references. The az cloud list names (AzureCloud, AzureUSGovernment, AzureChinaCloud) are accepted too.
  • authorityHost (optional): override the Entra ID authority host chosen by cloud. Rarely needed.
  • tenantId (optional): Azure AD tenant ID (directory ID)
  • clientId (optional): Service principal application (client) ID
  • clientSecret (optional): Service principal client secret (password)
  • oidcToken (optional): Explicit OIDC JWT token (auto-detected from platform if omitted)
  • id (optional): Instance identifier for multiple vaults
  • cacheTtl (optional): cache resolved values for the specified duration (e.g. "5m", "1h", "1d", or forever to cache until manually cleared). For cache mode behavior and CLI cache controls, see the Caching guide.
# @initAzure(vaultUrl="https://my-vault.vault.azure.net/")
# @initAzure(id=config, appConfigEndpoint="https://my-store.azconfig.io", defaultLabel=production)

Represents an Azure AD tenant ID (UUID format). This type is marked as @sensitive.

# @type=azureTenantId
AZURE_TENANT_ID=

Represents a service principal application (client) ID (UUID format). This type is marked as @sensitive.

# @type=azureClientId
AZURE_CLIENT_ID=

Represents a service principal client secret (password). This type is marked as @sensitive.

# @type=azureClientSecret
AZURE_CLIENT_SECRET=

Represents an App Configuration access-key connection string (Endpoint=...;Id=...;Secret=...). This type is marked as @sensitive and @internal.

# @type=azureAppConfigConnectionString
AZURE_APPCONFIG_CONNECTION_STRING=

Fetch a secret from Azure Key Vault.

Array args:

  • instanceId (optional): instance identifier to use when multiple plugin instances are initialized
  • secretName (optional): secret name or name with version using @ syntax. If omitted, uses the variable name (converted to kebab-case).
# Auto-infer secret name (DATABASE_URL -> "database-url")
DATABASE_URL=azureSecret()
# Explicit secret name
CUSTOM_SECRET=azureSecret("my-custom-secret")
# Specific version
API_KEY_V1=azureSecret("api-key@abc123def456")
# With instance ID
PROD_SECRET=azureSecret(prod, "database-url")

Fetch a single setting from Azure App Configuration. Key Vault references are dereferenced; feature flags are returned as their JSON string.

Array args:

  • instanceId (optional): instance identifier to use when multiple plugin instances are initialized
  • key (optional): setting key. If omitted, uses the config item key verbatim.

Key/value args:

  • label (optional): setting label. Overrides the instance defaultLabel; an empty string selects the unlabeled setting.
# Auto-infer key (DATABASE_URL -> "DATABASE_URL")
DATABASE_URL=azureAppConfig()
# Explicit key and label
API_URL=azureAppConfig("services:api:url", label=production)
# With instance ID
STAGING_API_URL=azureAppConfig(staging, "services:api:url")

List settings from Azure App Configuration and return them as a JSON object string, for use with @setValuesBulk(..., format=json).

Array args:

  • instanceId (optional): instance identifier to use when multiple plugin instances are initialized

Key/value args:

  • keyFilter (optional): key filter, * matches any characters (default *)
  • labelFilter (optional): label filter (default: defaultLabel if set, otherwise unlabeled settings only)
  • trimKeyPrefix (optional): prefix removed from each key before it is matched to a config item
# @setValuesBulk(azureAppConfigBulk(keyFilter="myapp:*", labelFilter=production, trimKeyPrefix="myapp:"), format=json)

  • Verify the secret exists: az keyvault secret list --vault-name my-vault
  • Remember: Azure uses hyphens, not underscores (use database-url not database_url)
  • Check for typos in the secret name
  • List settings in the store: az appconfig kv list --endpoint https://my-store.azconfig.io --auth-mode login
  • Check the label: an unlabeled setting and a labeled one with the same key are different settings. Set defaultLabel or pass label=
  • App Configuration keys are case-sensitive and used verbatim (no kebab-case conversion)
  • Check your RBAC role: az role assignment list --assignee <your-id> --scope <vault-scope>
  • Or check access policies: az keyvault show --name my-vault --query properties.accessPolicies
  • Ensure your identity has “Get” permission for secrets
  • For App Configuration, ensure the identity has the “App Configuration Data Reader” role on the store
  • Local dev: Run az login and ensure service principal env vars are empty
  • Azure-hosted apps: Verify Managed Identity is enabled and has Key Vault permissions
  • Other environments: Verify service principal credentials are correct and properly injected
  • Test identity: az account show
  • Verify the vault URL is correct
  • Check network access: Ensure firewall rules allow access from your IP/resource
  • Verify the vault exists in the specified subscription