Domain 4 of 4 · Chapter 1 of 4

Secure secrets with Azure Key Vault

What a vault holds, and the two planes you reach it through

A Python service that talks to PostgreSQL needs a password, and that password has to live somewhere. Put it in an environment variable and it is in your deployment template; put it in a settings file and it is in source control. Azure Key Vault's answer is to move the value into a managed store and leave behind only a URL such as https://contoso-kv.vault.azure.net, which is not itself a secret and can sit in a template in plain sight. Every other rule on this page follows from that one move, and from the question it forces: if the application holds no credential for the vault, what does the vault check before it hands the password over?

This page sits in the Secure, monitor, and troubleshoot Azure solutions domain, published by Microsoft at 20–25% of AI-200. Three sibling pages divide the rest of that domain, and the split is worth knowing before you read further: Azure App Configuration owns non-secret application settings, feature flags and its own store; distributed tracing and log queries own everything you inspect after the fact, including the vault's own diagnostic logs. This page owns Key Vault itself, which means the objects it stores, who is allowed to read them, how they rotate, and what happens when one is deleted.

Start with the container. Microsoft's authentication, requests and responses[1] reference names two container types under one resource provider. A vault is reached at <vault-name>.vault.azure.net and stores three kinds of thing, collectively called objects: cryptographic keys under /keys, secrets under /secrets, and certificates under /certificates. A Managed HSM is reached at <hsm-name>.managedhsm.azure.net and, per the object-type table in the keys, secrets and certificates overview[2], lists secrets and certificates as Not supported. It is a single-tenant, FIPS 140-3 Level 3 validated container for HSM-protected keys, not a higher-security drop-in for a vault. A workload whose job is to retrieve a password therefore still needs a vault no matter how strong its hardware requirement, because Managed HSM exposes no /secrets path to retrieve it from.

Within either container, every object has an object identifier, and its shape is worth memorising because three separate features depend on it:

https://<vault-name>.vault.azure.net/<object-type>/<object-name>/<object-version>

The version is a system-generated 32-character string. Microsoft calls an identifier that omits it a base identifier, and states plainly that you "can retrieve objects in Key Vault by specifying a version or by omitting the version to get the latest version of the object." Two names for one URL family, then: versioned pins one immutable version, versionless (the base identifier) follows whatever is current. Keep those two words; the rest of this page uses them for the SDK, for App Service references, and for customer-managed keys, and they mean the same thing in all three.

The last piece of vocabulary is the plane split, which the Azure RBAC guide[3] states directly. The control plane is "where you manage Key Vault itself": creating and deleting vaults, reading vault properties, changing access policies, all through management.azure.com. The data plane is "where you work with the data stored in a key vault": adding, deleting and modifying keys, secrets and certificates, through the vault's own hostname. Both authenticate through Microsoft Entra ID; they do not authorize the same way, and the next few sections are largely about that difference. The figure below shows the whole structure in one picture, from the resource provider down to the two identifier forms.

ProviderContainerObjectsIdentifierMicrosoft.KeyVaultVault<vault>.vault.azure.netManaged HSM<hsm>.managedhsm.azure.net/secrets/keys/certificates/keysHSM-protected onlyBase identifier/secrets/<name>resolves to the latest versionVersioned identifier/secrets/<name>/<version>pinned to one version+ /<version>
Container types, object types and identifier forms, using the names Microsoft's own object-identifier reference uses. Arrows read as contains, except the last, which reads as append a version segment.

Reading a secret from Python, and the 401 that is supposed to happen

Retrieval is four lines, and Microsoft's Python quickstart[4] writes them like this:

from azure.keyvault.secrets import SecretClient
from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()
client = SecretClient(vault_url="https://contoso-kv.vault.azure.net", credential=credential)
retrieved_secret = client.get_secret("pg-password")
print(retrieved_secret.value)          # the plaintext

Every term in those lines is load-bearing. vault_url is the data-plane endpoint from the previous section, and because the DNS suffix differs by cloud (.vault.azure.net in the public cloud, .vault.azure.cn and .vault.usgovcloudapi.net in the sovereign clouds), it belongs in configuration rather than as a literal in code that has to run in more than one of them. credential is any object that can produce an access token; DefaultAzureCredential is one such object and the next section is entirely about which identity it ends up using. get_secret returns a KeyVaultSecret whose .value attribute is the plaintext string.

A SecretClient binds to exactly one vault. Reading from a second vault means a second client, and reading a key or a certificate from the same vault means a different client class entirely: KeyClient for /keys and CertificateClient for /certificates. The three object types are separate surfaces with separate operations and separate permissions, so the SDK gives each its own client rather than one client with a mode switch. That is the same three-way split the identifier suffixes showed, expressed in code.

The version argument is where the base-identifier rule reappears. The SecretClient reference[5] gives the signature as get_secret(name, version=None) and documents the parameter as "(optional) Version of the secret to get. If unspecified, gets the latest version." So client.get_secret("pg-password") is the base identifier expressed in Python, and it will silently start returning a rotated value. Passing a version string is the pin. Two neighbouring methods matter for the same reason: set_secret(name, value) "create[s] a new version of the secret" when the name is already in use rather than overwriting, and update_secret_properties(name, version=None, enabled=...) changes properties "but can't change the secret's value."

Now the part that wastes investigations. The very first call a Key Vault SDK client makes on a fresh process is expected to return HTTP 401, and it is not a symptom of anything. Microsoft's authentication reference states it as a note: "Key Vault SDK clients for secrets, certificates, and keys in the first call to Key Vault do not provide an access token to retrieve tenant information. It's expected to receive an HTTP 401 using Key Vault SDK client where the Key Vault shows to the application the WWW-Authenticate header containing the resource and the tenant where it needs to go and ask for the token. If everything is configured correctly, the second call from the application to Key Vault will contain a valid token and will succeed." The header carries two parameters: authorization, the address of the OAuth2 authorization service, and resource, the resource name to request a token for. This is challenge-based authentication, and it is how the client discovers which tenant to authenticate against without you configuring one. The figure below traces the full round trip.

The practical consequence is a diagnostic rule: a single 401 at the start of a process is the handshake. A 401 that persists after the retry is a real credential problem, and a 403 Forbidden is different again, meaning the token was accepted but the principal lacks the data action or is blocked by the vault firewall. Reading the first 401 as "DefaultAzureCredential failed" sends you looking for a broken identity that is working perfectly.

get_secret calledclient holds no token yetRequest sentdeliberately unauthenticatedHTTP 401WWW-Authenticate headerToken requestedauthorization + resource paramsRequest retriedAuthorization: BearerHTTP 200secret value returned
The two-call handshake every Key Vault SDK client performs on its first request. The shaded 401 is a normal step, not a failure.

Which identity actually answers: the credential chain and the IMDS default

DefaultAzureCredential is not a credential. It is an ordered chain of credential types, and it returns the first token any of them can produce. The Python reference[6] lists the order: a service principal configured by environment variables, then WorkloadIdentityCredential if the workload identity webhook has set its variables, then an Azure managed identity, then the developer identities (a Windows shared token cache, Visual Studio Code, the Azure CLI, Azure PowerShell, the Azure Developer CLI) and finally brokered authentication. On a developer laptop the Azure CLI link usually wins; on an App Service or a container in Azure the managed-identity link wins first, so it never reaches the developer ones.

Three identity words get used interchangeably and should not be. A service principal is the directory object that represents an application rather than a person; Microsoft's authentication article describes its object ID as acting "like its username" and its client secret as acting "like its password." A managed identity is a service principal whose credential Azure creates, stores and rotates for you, which is why it is the recommended option: there is no client secret for anyone to leak. (The managed identities FAQ[7] adds a detail worth carrying: a managed identity has a service principal but no application object, so Microsoft Graph permissions must be granted directly to the service principal.) A credential chain is neither of those; it is the client-side selection logic that decides which of them to use.

That distinction is what resolves an apparent contradiction in the documentation. The quickstart says DefaultAzureCredential lets "your app use different authentication methods in different environments (local vs. production) without implementing environment-specific code", and the authentication article makes the same claim with different words. Read as a promise that nothing changes between environments, it is wrong, because the SDK reference documents a managed_identity_client_id keyword and an AZURE_CLIENT_ID environment variable that exist precisely to disambiguate. The accurate reading is narrower and still useful: the code is the same, the configuration is not. The line DefaultAzureCredential() never changes; which link of the chain fires, and which identity that link asks for, is decided by the environment around it.

So when do you have to intervene? The managed identities FAQ answers it in three cases, and they are the whole rule (the figure below lays them out):

  • A system-assigned identity is enabled. "Azure Instance Metadata Service (IMDS) defaults to the system assigned managed identity." There is no failure here, and this is the case people most often get wrong: if the vault role was granted to a user-assigned identity and you name no client ID, the token comes back bound to the system-assigned one and the vault returns 403. The symptom is an authorization error, not an authentication error.
  • No system-assigned identity, exactly one user-assigned. IMDS defaults to that single identity. The FAQ then warns that adding a second one later starts failing requests with Multiple user assigned identities exist, please specify the clientId / resourceId of the identity in the token request, and recommends naming an identity explicitly even when only one exists today.
  • No system-assigned identity, two or more user-assigned. "[Y]ou're required to specify a managed identity in the request."

The operational advice collapses to one line: set AZURE_CLIENT_ID to the client ID of the identity you actually granted the role to, or pass managed_identity_client_id= to the credential. It costs nothing when there is only one identity and it is the difference between working and not when there are several.

Token requested, no identity namedSystem-assigned enabledany number of user-assignedNo system-assigned,exactly one user-assignedNo system-assigned,two or more user-assignedSystem-assigned returneda user-assigned role is not usedset AZURE_CLIENT_ID to overrideThat identity returnedadding a second identity laterstarts failing the requestRequest failsMultiple user assignedidentities exist
The three cases the managed identities FAQ documents for a token request that names no identity. Only the third fails outright.

Authorizing the vault: Azure RBAC or vault access policies

Authentication proves who is calling; authorization decides what they may do, and on the data plane a vault runs exactly one of two authorization models. The switch is the vault property enableRbacAuthorization, and the ARM reference states the exclusion without ambiguity: when it is true "the key vault will use Role Based Access Control (RBAC) for authorization of data actions, and the access policies specified in vault properties will be ignored", and when false "the key vault will use the access policies specified in vault properties, and any policy stored on Azure Resource Manager will be ignored." There is no blended mode, and flipping the switch on a live vault is disruptive: the RBAC guide's own warning is that "[s]etting the Azure RBAC permission model invalidates all access policies permissions. It can cause outages when equivalent Azure roles aren't assigned."

Consideration Azure RBAC (recommended) Vault access policies (legacy)
Where the grant lives A role assignment in Azure Resource Manager A list of principals in the vault's own properties
Scopes available Management group, subscription, resource group, vault, or an individual secret, key or certificate The whole vault only
Inheritance A role assigned higher up applies downward None; the vault's list is the whole story
How you grant read access to secrets Assign Key Vault Secrets User Grant the Get secrets permission
Default for a new vault Yes, for vaults created with API version 2026-02-01 or later Only if enableRbacAuthorization is set to false at creation

Microsoft flags Azure RBAC as "(recommended)" on the secrets overview and access policies as "(legacy)", and as of API version 2026-02-01 the recommendation is also the default: "[w]hen you create a new vault with API version 2026-02-01 or later, the default access control model is Azure RBAC (enableRbacAuthorization = true)." That applies to create operations only. Existing vaults keep whatever they had, and a vault whose enableRbacAuthorization is null because it predates the property continues to use access policies. Both models remain fully supported, so "which model is this vault on?" is a real question to ask before debugging an access failure, not a formality.

On the RBAC side the two roles a developer meets are narrower than their names suggest, and the built-in role definitions[8] give the exact data actions:

  • Key Vault Secrets User carries Microsoft.KeyVault/vaults/secrets/getSecret/action ("Gets the value of a secret") and Microsoft.KeyVault/vaults/secrets/readMetadata/action ("List or view the properties of a secret, but not its value"). Read only. It cannot create a secret and it cannot write a rotated version.
  • Key Vault Secrets Officer carries Microsoft.KeyVault/vaults/secrets/*, which is every secret data action, described as "[p]erform any action on the secrets of a key vault, except manage permissions." A rotation function needs this one; the application that merely reads the result does not.

Two traps live in the gap between the planes. First, Key Vault Contributor is a control-plane role and grants no access to data; the RBAC guide says so in a note, and it is the most common over-grant on this objective. Second, and in the opposite direction, a principal with control-plane Contributor on an access-policy vault "can grant themselves access to the data plane by setting a Key Vault access policy", which is why Microsoft tells you to control that role tightly. Both roles look like they do the other one's job.

One further detail links this section to the eventing one below: changing the permission model is itself observable. The Event Grid schema lists Microsoft.KeyVault.VaultAccessPolicyChanged and notes that it "includes a scenario when Key Vault permission model is changed to/from Azure role-based access control", so an unexplained outage after a model flip is something you can alert on rather than discover from a support ticket.

exp, nbf and enabled: what actually stops a read

Set an expiration date on a leaked secret and you have not revoked it. This is the single most surprising behaviour on the whole objective, and the documentation states it directly enough that it is worth reading the source rather than a paraphrase.

A secret carries three lifecycle attributes. exp (expiration time) "sets the expiration time on or after which the secret data SHOULD NOT be retrieved, except in particular situations", and Microsoft adds that "[t]his field is for informational purposes only." nbf (not before) is the mirror image with the same informational caveat. enabled is the one with teeth: it "specifies whether the secret data can be retrieved", and when an operation falls between nbf and exp it "is only permitted if enabled is set to true."

That same enabled bullet then says something that reads like a flat contradiction of the informational caveat: "Operations outside the nbf and exp window are automatically disallowed, except in particular situations." Both sentences are on the same page, two bullets apart, and taken alone they cannot both be true. The reconciling clause is the one that appears in all three bullets and is easy to skim past: except in particular situations. The page names those situations in its own section, Date-time controlled operations, and for secrets there is no ambiguity left: "A secret's get operation works for not-yet-valid and expired secrets, outside the nbf / exp window. Calling a secret's get operation for a not-yet-valid secret can be used for test purposes. Retrieving (getting) an expired secret can be used for recovery operations."

So the two claims describe different scopes, and the safe formulation names the scope every time. For a get, expiry does not block retrieval; it is documented as an exception, deliberately, so an expired credential can still be recovered and a not-yet-valid one can still be tested. What exp genuinely buys you is not enforcement but signalling: it is the timestamp that drives the near-expiry event the rotation section relies on, and it is metadata a human or a policy can act on. Read as an access control, it is a false sense of security. Treat the general "automatically disallowed" statement as the rule for the object model at large, and the get exception as the documented, tested carve-out.

Revocation therefore has exactly one lever on this page. Disable the version:

client.update_secret_properties("pg-password", enabled=False)

A get against a disabled version fails, and it keeps failing until someone calls the same method with enabled=True. Note the argument shape from the SDK reference, update_secret_properties(name, version=None, ...): with no version it updates "the latest version", so disabling a compromised secret whose replacement has already been written is a two-step job, not a one-liner. And because the method "can't change the secret's value", disabling is a containment action, not a rotation. The next section is the rotation.

Rotation: three object types, three different answers to who makes the new value

"Autorotation" is one word covering three genuinely different mechanisms, and Microsoft's autorotation overview[9] separates them before describing any of them. Take the three in order, because picking the wrong one is a scenario question waiting to happen:

  • Keys rotate. A cryptographic key can carry a key rotation policy, and "Key rotation generates a new key version of an existing key with new key material." Key Vault produces the material itself, on a schedule you set. Policy settings are an expiry time, a rotation time whose "minimum value is seven days from creation and seven days from expiration time", and a notification time for the near-expiry event. Managing the policy needs Key Vault Crypto Officer. The guidance to consuming services is the base-identifier rule again: "[t]arget services should use versionless key URI to automatically refresh to the latest version of the key."
  • Certificates renew. Key Vault handles renewal itself, with an integrated certificate authority or by regenerating a self-signed certificate, on a configured percentage of lifetime or days before expiry.
  • Secrets do neither on their own. Key Vault cannot invent a valid PostgreSQL password, because only PostgreSQL can decide what its own credential is. Secret rotation is therefore "[e]vent-based triggering via Event Grid" plus "[i]ntegration with Azure Functions for custom rotation logic": Key Vault tells you when, your code decides what.

Use the words that go with each. A key is rotated (new material generated by the vault), a certificate is renewed, and a secret's underlying credential is regenerated by the backing service before a new version is written. Saying "Key Vault rotates my database password" collapses all three and hides the fact that you have code to write.

The eventing half is a closed, published list. Key Vault emits ten event types whose names all begin Microsoft.KeyVault, three per object type plus one for the vault itself. For secrets those three are Microsoft.KeyVault.SecretNewVersionCreated, Microsoft.KeyVault.SecretNearExpiry and Microsoft.KeyVault.SecretExpired; the key and certificate trios mirror them, and Microsoft.KeyVault.VaultAccessPolicyChanged completes the set. Near-expiry fires on a fixed schedule the overview states as "30 days before expiration" (for keys, the rotation policy's notification time makes it configurable). Two constraints on that page are easy to miss and both cause silent no-ops: notification events "are triggered only on new versions of secrets, keys and certificates", and "you must first subscribe to the event on your key vault in order to receive these notifications." A vault nobody subscribed to emits nothing you will ever see. Handlers can be an Azure Function, a Logic App, or your own webhook.

Put together, the rotation loop is the four steps in Microsoft's rotation tutorial[10], traced in the figure below: near-expiry event, Event Grid delivery to the function, the function generates a new credential and writes it as a new secret version, the function updates the backing service. The tutorial is candid about the hazard in the middle: "There can be a lag between steps 3 and 4. During that window, the secret in Key Vault can't authenticate to [the backing service]." That gap is the argument for the two-credential pattern, where the service supports a second active credential and rotation alternates between them. Notice what the loop never does: it does not overwrite. The prior version stays addressable at its own versioned identifier, and any consumer holding the base identifier starts resolving the new one with no redeploy, which is the whole reason step three is a write rather than an edit.

Two timing numbers in that tutorial deserve a caveat rather than memorisation. It says "[i]f any step fails, Event Grid retries for two hours", while the Event Grid delivery documentation describes a default time-to-live of 1440 minutes across 30 attempts; and it says a short expiration date "publishes a SecretNearExpiry event within 15 minutes", where the two-credential tutorial says "within several minutes." Microsoft's own pages disagree on both, so neither number is safe to treat as a rule. What is stable, and what this page keys on, is the Key Vault side: which events exist, what triggers them, and that your handler owns the new credential. Event Grid's delivery guarantees, retry schedule and dead-lettering are taught on the Event Grid page, which owns that half of the contract.

Secret nears expiry30 days before the expiry dateKey Vault publishesMicrosoft.KeyVault.SecretNearExpiryEvent Grid deliversto the subscribed functionYour function makesa new credentialin the backing serviceset_secret writesa new versionprior version stays addressableBase-identifierconsumers followno redeploy
The near-expiry rotation loop from Microsoft's single-credential tutorial; the shaded boxes are the steps your own code owns.

Key Vault references: resolving a secret without writing code

There is a second way to get a secret into an application, and on App Service, Azure Functions and Logic Apps (Standard) it is usually the better one. Set an application setting or connection string to a Key Vault reference and the platform resolves it before your process starts, injecting the plain value as an ordinary setting. Microsoft's summary is four words long: "No code changes are required."

Keep the two paths named apart. A reference is @Microsoft.KeyVault(...), resolved by the platform with no SDK involved. A retrieval is SecretClient.get_secret, code you wrote and control. Both end with the value in your process; only one of them is something you can put a breakpoint in.

The reference documentation[11] gives two syntaxes, and the version rule is the base-identifier rule once more:

@Microsoft.KeyVault(SecretUri=https://contoso-kv.vault.azure.net/secrets/pg-password)
@Microsoft.KeyVault(VaultName=contoso-kv;SecretName=pg-password;SecretVersion=ec96f020...)

In the SecretUri form the version segment is optional; in the VaultName form SecretVersion is the optional part and VaultName and SecretName are required. Omit the version and "the app uses the latest version that exists in the key vault."

Resolution runs as an identity, and which one is a configuration decision: "Key vault references use the app's system-assigned identity by default, but you can specify a user-assigned identity" by setting the keyVaultReferenceIdentity property to that identity's resource ID. Note the blast radius stated one line later: "This setting applies to all Key Vault references for the app." It is an app-wide switch, not a per-setting one. Whichever identity resolves them needs read access on the vault, and the docs name the grant for both authorization models: assign Key Vault Secrets User under Azure RBAC, or the Get secrets permission under a vault access policy.

Rotation reaches a versionless reference on a schedule rather than instantly, and the numbers are worth holding: "[w]hen newer versions become available, such as with rotation, the app is automatically updated and begins using the latest version within 24 hours", because "App Service caches the values of the Key Vault references and refetches them every 24 hours." Two things shorten that. "Any configuration change to the app causes an app restart and an immediate refetch of all referenced secrets", and an authenticated POST to https://management.azure.com/[Resource ID]/config/configreferences/appsettings/refresh?api-version=2022-03-01 forces resolution on demand.

The failure mode is the part to commit to memory, because it is counter-intuitive and it is exactly what a well-built exam item probes. An unresolved reference does not leave the setting empty and does not stop the app from starting: "[i]f a reference isn't resolved properly, the reference string is used instead. Here's an example, @Microsoft.KeyVault(...)." Your code receives the literal reference text as though it were the password, and the first symptom is an authentication failure against the backing service with a nonsense credential, several layers away from the actual cause. Microsoft lists the usual causes as a misconfigured access policy, a secret that no longer exists, or a syntax error in the reference, and points at the portal's per-setting status dialog and the Key Vault Application Settings Diagnostics detector for the rest.

One boundary note. Configuring the identity on the function app, choosing a hosting plan, and everything else about how a function app is deployed belong to the function app configuration page. What this page owns is the other side of that contract: what the secret must be, which permission the identity needs on the vault, and what the platform does when the resolution fails.

Throughput: the throttle is the vault, and the fix is caching

Key Vault was built as a deployment-time store and is now routinely used at runtime, and Microsoft says as much: "Many applications and services use Key Vault similar to a database. However, the current service limits are not designed to support such high throughput scenarios." Design a service that calls get_secret on every request and you will meet HTTP 429 long before you meet anything else. When a threshold is crossed "Key Vault limits any further requests from that client, returns HTTP status code 429 (Too many requests), and the request fails."

The scope of that budget decides which fixes are available, so read the service limits[12] table header literally: transactions are counted per vault per region. Secrets and most other operations share a ceiling of 4,000 transactions per 10 seconds, while creating a secret, importing a certificate and importing a key share a much smaller 300 collectively. Key transactions are weighted rather than flat, and the reference works the arithmetic: because 4,096-bit HSM keys allow 250 GET transactions per 10 seconds against 2,000 for 2,048-bit, "it's eight times more expensive to use 4,096-bit keys compared to 2,048-bit keys because 2,000/250 = 8."

The footnote on that table is the constraint people plan around and then discover too late: "A subscription-wide limit for all transaction types is five times per key vault limit." Splitting traffic across vaults is a real lever, but only up to roughly five vaults' worth of throughput inside one subscription, after which the subscription ceiling binds and adding vaults buys nothing. The throttling guidance says the same thing from the operational side: "Do not send more than the subscription limit to the Key Vault service in a single Azure region."

Notice what the limits table does not have: a SKU or vault-size column for secret and vault transactions. There is no bigger-vault knob to reach for, and accordingly every lever the throttling guidance[13] offers is of a different shape. These are the ones to recognise in a scenario:

  • Cache in memory. "Cache the secrets you retrieve from Azure Key Vault in memory, and reuse from memory whenever possible. Re-read from Azure Key Vault only when the cached copy stops working (for example, because it got rotated at the source)." A failed authentication against the backing service is the cache-invalidation signal, which is a neat inversion of the usual time-to-live approach and pairs exactly with the rotation model above.
  • Divide across vaults and regions. The guidance's own worked example: "[i]f you have five apps, each in two regions, then we recommend 10 vaults each containing the secrets unique to app and region." One vault per security and availability domain.
  • Fan out. Where many nodes need the same secret, have one entity read it and distribute to the rest, caching only in memory.
  • Remove the secret entirely. "If you use Key Vault to store credentials for a service, check if that service supports Microsoft Entra authentication to authenticate directly." The fastest Key Vault call is the one you do not make.
  • Back off exponentially, and never retry immediately. The documented client pattern is 1, 2, 4, 8 then 16 seconds. Immediate retries make it worse, because "[a]ll requests accrue against your usage limits" (the one relief is that "[f]ailed requests that return a 429 do not count towards the throttle limits").

One more piece of routing that follows from the limits table rather than from performance folklore: CREATE key and RELEASE key share a throttling category capped at 10 transactions per 10 seconds for HSM-protected keys, so for high-throughput secure key release Microsoft directs you to Managed HSM, which "provides dedicated HSM instances with higher limits." That is the one scenario where Managed HSM is the answer to a throughput question rather than a compliance one.

Deleting: soft-delete is retention, purge protection is prevention

The two delete-protection settings are constantly treated as one feature with a volume knob, and they are not. Soft-delete controls how long a deleted thing stays recoverable. Purge protection controls whether anyone can cut that window short. Getting the pair right is a design decision you make once at vault creation and cannot fully undo, so it is worth being precise.

Start with the shape of a deletion. Per the soft-delete overview[14], "[t]wo operations must be made to permanently delete a secret. First a user must delete the object, which puts it into the soft-deleted state. Second, a user must purge the object in the soft-deleted state." A soft-deleted object is not readable: it "can only be listed, recovered, or forcefully/permanently deleted." The second operation needs its own grant, the purge permission, available through the built-in Key Vault Purge Operator role. The states and the transitions between them are in the figure below.

Soft-delete is retention, and its parameters are fixed early:

  • It is on by default. "When creating a new key vault, soft-delete is on by default. Once soft-delete is enabled on a key vault, it can't be disabled."
  • Retention is "a configurable period of 7 to 90 calendar days", defaulting to 90. The ARM property softDeleteRetentionInDays "accepts >=7 and <=90."
  • "The retention policy interval can only be configured during key vault creation and can't be changed afterwards." Deciding 7 days on day one and wanting 90 later means a new vault.
  • The vault name is reserved for the whole window: "[y]ou can't reuse the name of a key vault that was soft-deleted, until the retention period expires." Re-running a deployment script against a name you just deleted fails, and the error is not obviously about soft-delete.

Purge protection is prevention, and it is a separate, opt-in commitment:

  • "Purge protection is an optional Key Vault behavior and is not enabled by default. Purge protection can only be enabled once soft-delete is enabled."
  • With it on, "a vault or an object in the deleted state can't be purged until the retention period passes." Not by an administrator, not by the subscription owner. The ARM reference puts it as "only the Key Vault service may initiate a hard, irrecoverable deletion."
  • Enabling it is one-way: "Enabling this functionality is irreversible - that is, the property does not accept false as its value."

The security argument for the pair is the difference between them. Soft-delete on its own still permits an immediate purge, so an attacker or a bad script with the purge permission can destroy secrets in two calls and the retention window never protects anything. Purge protection is what converts retention into a guarantee, at the cost of an irreversible setting and a vault name you cannot recycle for up to 90 days. That trade is why Microsoft notes that "[m]ost Azure services that integrate with Azure Key Vault, such as Storage, require purge protection to prevent data loss" before they will use a vault for customer-managed keys.

One recovery caveat that catches teams mid-incident: recovering a soft-deleted vault does not put everything back. "When a Key Vault is soft-deleted, services that are integrated with the Key Vault are deleted. For example: Azure RBAC roles assignments and Event Grid subscriptions. Recovering a soft-deleted Key Vault does not restore these services. They must be recreated." So a recovered vault comes back with its secrets and without the role assignment your app reads them through, and without the event subscription your rotation function depends on. Plan the recovery as re-running your infrastructure deployment, not as flipping a switch.

Activereadable and listableSoft-deletedrecoverable for 7 to 90 dayslist, recover or purge onlyRecoveredback to Activerole assignments and Event Gridsubscriptions must be recreatedPurgedpermanent, no recoverypurge protection blocks thisuntil the retention window endsdeleterecoverpurge
Object states under soft-delete's two-operation deletion model. Purge protection adds no state; it gates the transition on the right.

Telling the scenarios apart

Scenario questions on this objective rarely ask what a feature does. They describe a symptom and ask which of four plausible mechanisms produced it, so the useful skill is a set of discriminators. These are the ones this page has earned, each pointing back at the section that establishes it.

A 401 versus a 403. A single 401 on a process's first vault call is the challenge handshake and needs no fix. A 401 that survives the retry is a token problem: no identity, wrong tenant, expired credential. A 403 means the token was fine and the principal was refused, which routes you to the authorization model, the role assignment, or the vault firewall, never to DefaultAzureCredential.

The app read a secret it should not have, or could not read one it should. Ask which identity the token was issued to before you touch the role assignment. With a system-assigned identity enabled and no AZURE_CLIENT_ID set, IMDS returns the system-assigned identity even when the vault role sits on a user-assigned one, and the result is a 403 that looks like a missing grant.

"We set an expiry and the old credential still works." That is documented behaviour for get, not a bug and not a propagation delay. Disabling the version is the only lever that stops a read.

"Rotation happened but the app kept using the old value." Three candidates, and the wording separates them. A versioned identifier or reference pins by design and will never move. A versionless App Service reference moves, but on a cache with a 24-hour refetch, so a restart or a configuration change is the fast path. Code that read the secret once at startup and held it forever is the third, and it is the one no platform setting fixes.

"The app is getting a nonsense password." If the workload uses Key Vault references, check whether the literal @Microsoft.KeyVault(...) string is being injected: an unresolved reference falls back to its own text rather than to an empty value, so the failure surfaces at the backing service.

429 under load. The budget is per vault per region, so the answers are caching, splitting across vaults and regions, and backing off. "Scale the vault up" is not an option that exists, and "add more vaults in the same subscription" stops helping once the subscription ceiling, five times a single vault's limit, is reached.

"The secret is gone and we need it back." Ask whether it was deleted or purged. Deleted means soft-deleted and recoverable within the retention window. Purged means gone, and if purge protection was on, purged is impossible before that window elapses. If a whole vault was recovered, the follow-up is that role assignments and Event Grid subscriptions came back missing.

"We need HSM-grade protection for a password." Managed HSM does not store secrets. The answer is a Premium vault, whose HSM-protected keys live alongside the secrets, not a Managed HSM instance.

Three ways a workload obtains a credential, and when each is the right answer

ConsiderationNo secret (Microsoft Entra token to the service)Key Vault reference (platform resolves it)SDK retrieval (SecretClient.get_secret)
Who performs the exchangeYour SDK client for the target service, using its managed identityThe App Service or Functions platform, before your process startsYour code, at whatever moment you call it
Where the value appearsNowhere; there is no long-lived credentialAs an ordinary app setting or environment variableIn a variable in your process memory
What the identity needsA role on the target service, not on any vaultKey Vault Secrets User on the vault, or Get secrets under an access policyThe same vault permission as the reference path
How a rotation reaches itNot applicable; tokens are short-lived and reissuedVersion-less reference refetched within 24 hours, immediately on a config change or restartOn your next call, if you used the base identifier and did not cache indefinitely
How it failsToken request fails, with the identity named in the errorSilently: the literal `@Microsoft.KeyVault(...)` string is injected as the valueA raised exception you can catch, retry, and log
Reach for it whenThe service supports Microsoft Entra authentication at allThe secret is startup configuration on a platform that supports referencesThe host has no app-settings mechanism, or you need the value on demand

Decision tree

Target service accepts aMicrosoft Entra token?No secretmanaged identity straight to the serviceStartup configuration on App Service,Functions or Logic Apps (Standard)?Key Vault reference@Microsoft.KeyVault(SecretUri=...), no versionNeeded on demand, or on a hostwith no app-settings mechanism?SDK retrievalSecretClient.get_secret, cached in memoryHSM-protected keys only,never secret retrieval?Managed HSMa vault is still needed for any secretRe-examine the requirementa value that fits none of these is usually not asecret: App Configuration owns thoseyesyesyesyesnononono

Sharp facts the exam loves — give these one last read before exam day.

Cheat sheet

Sharp facts the exam loves — scan these before test day.

SecretClient authenticated with DefaultAzureCredential retrieves a secret by name from a vault

Instantiate SecretClient(vault_url="https://.vault.azure.net", credential=DefaultAzureCredential()) and call get_secret(name) to read a secret at runtime; DefaultAzureCredential uses the app's managed identity in Azure and developer credentials locally, so no secret or connection string is embedded in code.

Trap Passing a raw API key or hard-coded credential to SecretClient defeats the purpose — the client is meant to be reached with a managed identity via DefaultAzureCredential.

11 questions test this
A versionless secret identifier returns the current version; a versioned identifier pins one exact version

A secret identifier without a version segment (.../secrets/), which Key Vault calls a base identifier, resolves to the latest version of the object, while appending a version (.../secrets//) pins that immutable version. get_secret with no version argument returns the latest version.

Trap Pinning a versioned URI means a rotated secret is NOT picked up; use the versionless identifier when you want rotation to flow through automatically.

6 questions test this
Keys, secrets, and certificates each have a dedicated Key Vault client

The SDK exposes SecretClient for secrets, KeyClient for cryptographic keys, and CertificateClient for certificates; they are separate clients against the same vault endpoint because the three object types have distinct operations and permissions.

Expiry and not-before are informational for a secret get; only enabled=false blocks retrieval

The exp and nbf attributes on a Key Vault secret are informational for a get - the docs carry a dedicated Date-time controlled operations section stating that a get works for not-yet-valid and expired secrets, so they can be used for test and recovery scenarios. Only enabled=false blocks a get. A get against a disabled version fails and the value cannot be read until the version is re-enabled with update_secret_properties(name, enabled=True).

Trap Setting an expiration date does not stop an application from reading the secret - code that must hard-stop access to a compromised credential has to DISABLE the secret version, not merely expire it.

3 questions test this
A Key Vault SDK client's first call returns 401 by design - the challenge that discovers the tenant

Key Vault SDK clients for secrets, keys and certificates send their first request without an access token on purpose: Key Vault answers HTTP 401 with a WWW-Authenticate header naming the authorization endpoint and the resource, and the client then retries with a valid token. A 401 on a process's first Key Vault call is the expected handshake, not a misconfiguration; only a 401 that persists after the retry indicates a real credential or access problem.

Trap A 401 in traces or logs for the first Key Vault call does NOT mean DefaultAzureCredential failed - the handshake is how the client learns which tenant to authenticate against, so chasing it as a credential bug wastes the investigation.

1 question tests this
A SecretClient binds to one vault endpoint, and Managed HSM has no secrets surface at all

A SecretClient is constructed against exactly one vault's data-plane endpoint, and the DNS suffix is cloud-specific (.vault.azure.net in the public cloud, .vault.azure.cn and .vault.usgovcloudapi.net in the sovereign clouds), so the vault URL must be configuration rather than a literal in code that runs across clouds. Managed HSM is a keys-only container reached at .managedhsm.azure.net: it supports HSM-protected keys and nothing else, so secret retrieval has no Managed HSM equivalent.

Trap Managed HSM is not a higher-security drop-in for a vault that stores secrets - it exposes only /keys, so a workload that must RETRIEVE secrets still needs a key vault no matter how strong its HSM requirement is.

5 questions test this
Key Vault throttles per vault per region, and the subscription ceiling is only five times one vault

Key Vault's transaction budget is enforced per vault per region and answers HTTP 429 once a client exceeds it, so a single high-traffic vault is the bottleneck rather than the subscription. Retrieval scales by caching secrets in memory and re-reading only when the cached copy stops working, and by splitting traffic across multiple vaults - but the subscription-wide ceiling is only five times a single vault's limit, so adding vaults inside one subscription stops helping.

Trap Retrying a 429 immediately does not help; and because the throttle scope is the vault resource, 'use a bigger vault' is not an available move - the levers are caching, more vaults, and eventually more subscriptions.

Key Vault emits Event Grid events such as SecretNearExpiry and SecretNewVersionCreated to drive rotation

Key Vault publishes lifecycle events (Microsoft.KeyVault.SecretNearExpiry, SecretExpired, SecretNewVersionCreated) to Event Grid; subscribing an Azure Function to SecretNearExpiry lets you generate a new credential in the backing service and add it as a new secret version before the old one expires.

Trap Polling the vault on a timer to check expiry is the anti-pattern the event model replaces — rotation should be event-driven off SecretNearExpiry, not scheduled scanning.

14 questions test this
Rotating a secret creates a new version, and versionless consumers pick it up automatically

Rotation does not overwrite in place; set_secret adds a new version and the prior version stays recoverable. Consumers that reference the secret by its versionless identifier begin resolving the new version automatically, which is what allows rotation without a redeploy.

Trap Thinking rotation overwrites the secret in place, so the previous value is gone.

6 questions test this
Cryptographic keys support a built-in automatic rotation policy

For keys (not secrets), Key Vault offers a rotation policy that regenerates the key on a defined interval and can fire a near-expiry Event Grid notification; secret rotation of external credentials still relies on a custom rotation handler.

An app setting of the form @Microsoft.KeyVault(...) resolves a secret at runtime without code

Set an App Service or Functions application setting to @Microsoft.KeyVault(SecretUri=) (or @Microsoft.KeyVault(VaultName=...;SecretName=...)); the platform resolves it from Key Vault using the app's managed identity and injects the plain value as an environment variable, so the secret never appears in source control or configuration files.

Trap The app's managed identity still needs Get permission on the vault (Key Vault Secrets User under RBAC); without it the reference FAILS TO RESOLVE and the platform injects the literal '@Microsoft.KeyVault(...)' reference string as the setting value — it is never blank.

9 questions test this
A versionless Key Vault reference automatically picks up a rotated secret; a versioned one pins it

When the reference omits the version, App Service periodically refreshes the resolved value (within about a day, or immediately on an application-settings change or restart) so a rotated secret flows in with no redeploy and no downtime; a versioned reference stays fixed to that version.

Trap Expecting a version-pinned Key Vault reference to follow a rotated secret.

2 questions test this
Grant the workload's managed identity the Key Vault Secrets User role for read access

Assign the app's system- or user-assigned managed identity the data-plane role Key Vault Secrets User (get/list secrets) scoped to the vault; the app then authenticates with that identity via DefaultAzureCredential and reads secrets with no stored credential.

Trap Key Vault Secrets User grants only read (get/list); creating or rotating secrets requires Key Vault Secrets Officer — don't over- or under-grant the role.

11 questions test this
A vault uses either Azure RBAC or vault access policies, not both at once

Each vault's permission model is set by enableRbacAuthorization: Azure RBAC uses role assignments that inherit from subscription/resource-group scope, while the legacy vault-access-policy model assigns per-principal permissions on the vault itself. Microsoft recommends RBAC for consistent, scopeable management.

Trap Expecting a leftover vault access policy to still grant access once the vault moves to Azure RBAC.

10 questions test this
DefaultAzureCredential chain and user-assigned identity client-id

DefaultAzureCredential tries an ordered chain of credentials - environment variables, then workload/managed identity, then developer credentials (Azure CLI / VS Code) - so the same SDK code authenticates locally and in Azure with no code change. Configuration is what differs: when the workload must authenticate as a USER-ASSIGNED managed identity, name it by client id (the AZURE_CLIENT_ID environment variable, or ManagedIdentityCredential(client_id=...)), because IMDS resolves an unnamed request to the system-assigned identity when one is enabled and rejects it outright when several user-assigned identities exist.

Trap Supplying no client id does NOT reliably fail: with a system-assigned identity enabled, IMDS defaults to it, so a role granted only to a user-assigned identity produces a 403 authorization error rather than an authentication error. The request fails outright only when no system-assigned identity is enabled and two or more user-assigned identities exist.

6 questions test this
Soft-delete retains deleted vaults and secrets for a retention period so they can be recovered

Soft-delete (enabled by default and not disableable) keeps a deleted vault or secret in a recoverable state for its configured retention period; you recover the object during that window instead of losing it permanently.

Trap Assuming soft-delete can be switched off to make a delete immediate.

12 questions test this
Purge protection blocks permanent deletion until the retention period elapses

With purge protection enabled, a soft-deleted vault or secret cannot be purged (permanently deleted) before its retention period ends, defeating an attacker or accident that tries to erase secrets immediately. Purge protection cannot be turned off once enabled.

Trap Soft-delete alone still allows an immediate purge; only purge protection prevents early permanent deletion — the two settings are distinct.

10 questions test this

Also tested in

References

  1. Authentication, requests, and responses
  2. Azure Key Vault keys, secrets and certificates overview
  3. Provide access to Key Vault keys, certificates, and secrets with Azure RBAC
  4. Quickstart: Azure Key Vault secret client library for Python
  5. azure.keyvault.secrets.SecretClient class reference
  6. azure.identity.DefaultAzureCredential class reference
  7. Managed identities for Azure resources frequently asked questions FAQ
  8. Azure built-in roles for Security
  9. Understanding autorotation in Azure Key Vault
  10. Rotation tutorial for resources with one set of authentication credentials
  11. Use Key Vault references as app settings in Azure App Service
  12. Azure Key Vault service limits
  13. Azure Key Vault throttling guidance
  14. Azure Key Vault soft-delete overview