Azure Error Guide: 'Forbidden' Key Vault Secrets Get Permission Denied
Fix the Azure Key Vault Forbidden / 'does not have secrets get permission' error: diagnose access policies vs RBAC, wrong identity, firewall rules, and tenant mismatches.
- #azure
- #troubleshooting
- #errors
- #keyvault
Overview
A Key Vault Forbidden error happens when the caller successfully authenticates to Entra ID but is not authorized to read the secret. The vault’s permission model — either legacy access policies or Azure RBAC — has no entry granting the caller secrets get, or a network rule blocks the request before the data-plane check. Azure returns 403 and the application fails to start.
You will see this in the SDK exception or az output:
ERROR: (Forbidden) The user, group or application 'appid=8f3b2c1d-aaaa-bbbb-cccc-1234567890ab;oid=2c4e6a8b-...;iss=https://sts.windows.net/<tenant>/' does not have secrets get permission on key vault 'prod-kv;location=eastus'. For help resolving this issue, please see https://go.microsoft.com/fwlink/?linkid=2125287
And in the activity log / SDK error body for an RBAC-mode vault:
{"error":{"code":"Forbidden","message":"Caller is not authorized to perform action on resource.\nIf role assignments, deny assignments or role definitions changed recently, please observe propagation time.\nCaller: appid=8f3b2c1d-aaaa-bbbb-cccc-1234567890ab;oid=2c4e6a8b-...\nAction: 'Microsoft.KeyVault/vaults/secrets/getSecret/action'\nResource: '/subscriptions/<sub>/resourcegroups/rg-prod/providers/microsoft.keyvault/vaults/prod-kv/secrets/db-password'","innererror":{"code":"ForbiddenByRbac"}}}
It occurs at the data plane — after a token is issued — so authentication is fine. The failure is purely an authorization gap on the vault, and the exact cause depends on whether the vault uses access policies or RBAC.
Symptoms
- App throws
Forbidden/ 403 reading a secret, but the same identity can log in fine. az keyvault secret showreturns(Forbidden) ... does not have secrets get permission.- The error references an
appid/oidthat does not match who you think is calling. - Works from one machine/network but fails from another (firewall).
az keyvault secret show --vault-name prod-kv --name db-password --query value -o tsv
ERROR: (Forbidden) The user, group or application 'appid=8f3b2c1d-...;oid=2c4e6a8b-...' does not have secrets get permission on key vault 'prod-kv;location=eastus'.
Code: Forbidden
az keyvault show --name prod-kv --query "properties.enableRbacAuthorization" -o tsv
true
The enableRbacAuthorization flag tells you which permission model to debug — true means RBAC, false/empty means access policies.
Common Root Causes
1. Missing access policy (vault in access-policy mode)
When enableRbacAuthorization is false, authorization comes from properties.accessPolicies. If the caller’s object ID has no policy granting secrets get, every read is Forbidden.
az keyvault show --name prod-kv \
--query "properties.accessPolicies[].{oid:objectId, secrets:permissions.secrets}" -o table
Oid Secrets
------------------------------------ ------------------
1a1a1a1a-bbbb-cccc-dddd-eeeeeeeeeeee ['get', 'list']
The caller’s oid (2c4e6a8b-...) is not in the list — no policy, so no access.
2. Missing RBAC role (vault in RBAC mode)
When enableRbacAuthorization is true, access policies are ignored entirely. The caller needs a role like Key Vault Secrets User assigned at the vault (or higher) scope.
VAULT_ID=$(az keyvault show --name prod-kv --query id -o tsv)
az role assignment list --scope "$VAULT_ID" \
--query "[].{principal:principalId, role:roleDefinitionName, scope:scope}" -o table
Principal Role Scope
------------------------------------ ---------------------------- --------------------------------
1a1a1a1a-bbbb-cccc-dddd-eeeeeeeeeeee Key Vault Administrator .../vaults/prod-kv
The caller’s principal ID is absent. A Reader role does NOT grant data-plane access — only data-plane roles like Key Vault Secrets User work.
3. Wrong identity (managed identity vs user)
The error’s appid/oid reveals who actually called. A common trap: you granted access to your user account but the app runs under a managed identity (or vice versa), so the identity in the policy never matches the caller.
# What identity is the error referencing? Resolve the oid:
az ad sp show --id 2c4e6a8b-1111-2222-3333-444455556666 \
--query "{name:displayName, type:servicePrincipalType, appId:appId}" -o table
Name Type AppId
------------------------- ------------- ------------------------------------
prod-app-mi ManagedIdentity 8f3b2c1d-aaaa-bbbb-cccc-1234567890ab
If the access policy/role was granted to john@contoso.com but the caller is the prod-app-mi managed identity, authorization fails — grant it to the MI’s object ID.
4. Firewall / private endpoint blocking the caller
Even with correct permissions, the vault’s network rules can reject the request before the data plane evaluates it. A vault with defaultAction: Deny blocks any source IP/VNet not explicitly allowed.
az keyvault network-rule list --name prod-kv \
--query "{default:defaultAction, ips:ipRules[].value, vnets:virtualNetworkRules[].id}" -o jsonc
{
"default": "Deny",
"ips": [ "203.0.113.10/32" ],
"vnets": [ "/subscriptions/<sub>/.../subnets/aks-subnet" ]
}
If the caller’s egress IP or subnet is not in the allow list, it gets a 403 (or ForbiddenByFirewall in the inner error) regardless of policy/RBAC.
5. Purge-protection / soft-delete state on the secret
A secret can be present but in a soft-deleted or disabled state, which surfaces as access errors. A disabled secret version cannot be read, and a deleted secret returns Forbidden/SecretNotFound until recovered.
az keyvault secret show --vault-name prod-kv --name db-password \
--query "attributes.enabled" -o tsv
az keyvault secret list-deleted --vault-name prod-kv \
--query "[?name=='db-password'].{name:name, recoveryId:recoveryId}" -o table
false
Name RecoveryId
----------- -----------------------------------------------------------
db-password https://prod-kv.vault.azure.net/deletedsecrets/db-password
The secret is disabled (enabled: false) or sitting in the deleted state — re-enable or recover it.
6. Wrong tenant
If you authenticated against the wrong tenant, the token’s iss (issuer) points at a different directory than the vault, and the caller’s object ID does not exist in the vault’s tenant. The error’s iss=https://sts.windows.net/<tenant>/ reveals the mismatch.
az account show --query "{tenantId:tenantId, user:user.name, sub:name}" -o table
az keyvault show --name prod-kv --query "properties.tenantId" -o tsv
TenantId User Sub
------------------------------------ ------------------ ----------------
99999999-aaaa-bbbb-cccc-dddddddddddd john@contoso.com Contoso Prod
11111111-aaaa-bbbb-cccc-dddddddddddd
The logged-in tenant (9999...) differs from the vault’s tenant (1111...) — no identity in the wrong tenant can be authorized. Re-login with az login --tenant <vault-tenant>.
Diagnostic Workflow
Step 1: Determine the vault’s permission model
az keyvault show --name <VAULT> \
--query "{rbac:properties.enableRbacAuthorization, tenant:properties.tenantId, net:properties.networkAcls.defaultAction}" -o jsonc
rbac: true means debug RBAC; otherwise debug access policies. Note the tenant and network default action.
Step 2: Identify the real caller from the error
# Take the oid/appid from the Forbidden message and resolve it
az ad sp show --id <APPID_OR_OID> --query "{name:displayName, type:servicePrincipalType}" -o table
Confirm the caller is the identity you intended to grant (user vs managed identity vs service principal).
Step 3: Check the authorization grant for that identity
# RBAC mode
VAULT_ID=$(az keyvault show --name <VAULT> --query id -o tsv)
az role assignment list --scope "$VAULT_ID" --assignee <OID> -o table
# Access-policy mode
az keyvault show --name <VAULT> \
--query "properties.accessPolicies[?objectId=='<OID>'].permissions.secrets" -o jsonc
Empty output means the grant is missing — that is your root cause.
Step 4: Verify the network path
az keyvault network-rule list --name <VAULT> -o jsonc
# Confirm your egress IP is allowed (or the vault uses a private endpoint you can reach)
curl -s https://api.ipify.org
If defaultAction is Deny and your IP/subnet is not listed, fix the network rule before retrying.
Step 5: Grant access and re-test
# RBAC mode
az role assignment create --assignee <OID> \
--role "Key Vault Secrets User" --scope "$VAULT_ID"
# Access-policy mode
az keyvault set-policy --name <VAULT> --object-id <OID> --secret-permissions get list
# Wait for propagation, then retry
az keyvault secret show --vault-name <VAULT> --name <SECRET> --query value -o tsv
Example Root Cause Analysis
A container app orders-api running on AKS starts failing with Forbidden when reading db-password from prod-kv. The developer insists they granted themselves access to the vault yesterday.
The Forbidden message references a specific identity:
The user, group or application 'appid=8f3b2c1d-...;oid=2c4e6a8b-...' does not have secrets get permission on key vault 'prod-kv;location=eastus'.
Resolving that object ID shows it is not the developer:
az ad sp show --id 2c4e6a8b-1111-2222-3333-444455556666 \
--query "{name:displayName, type:servicePrincipalType}" -o table
Name Type
------------------ ---------------
orders-api-mi ManagedIdentity
The caller is the workload’s managed identity, not the developer’s user account. Checking the vault’s model and the MI’s grant:
az keyvault show --name prod-kv --query "properties.enableRbacAuthorization" -o tsv
VAULT_ID=$(az keyvault show --name prod-kv --query id -o tsv)
az role assignment list --scope "$VAULT_ID" --assignee 2c4e6a8b-1111-2222-3333-444455556666 -o table
true
(empty)
The vault is in RBAC mode and the managed identity has no role assignment — the developer granted access to their own user, which RBAC mode applies to the data plane only if the user reads it interactively. The workload identity was never granted.
Fix: assign the data-plane role to the managed identity and wait for propagation:
az role assignment create --assignee 2c4e6a8b-1111-2222-3333-444455556666 \
--role "Key Vault Secrets User" --scope "$VAULT_ID"
After role propagation, orders-api reads the secret and starts cleanly.
Prevention Best Practices
- Standardize on one permission model per environment (prefer RBAC) so you always know whether to debug
accessPoliciesoraz role assignment. - Grant access to the identity the workload actually runs as — usually a managed identity — not to a developer’s user account.
- Always read the
appid/oidin the Forbidden message first; it tells you exactly who called and which tenant issued the token. - Use data-plane roles (
Key Vault Secrets User,Key Vault Administrator) for RBAC vaults —Reader/Contributorgrant management-plane access only. - Keep vault network rules in config management and document which subnets/IPs each workload egresses from, so firewall denials are obvious.
- For ad-hoc triage, the free incident assistant can turn a Forbidden body into the likely identity/model gap. See more in Azure guides.
Quick Command Reference
# Which permission model + tenant + network posture?
az keyvault show --name <VAULT> --query "{rbac:properties.enableRbacAuthorization, tenant:properties.tenantId, net:properties.networkAcls.defaultAction}" -o jsonc
# Who is actually calling? (resolve oid/appid from the error)
az ad sp show --id <APPID_OR_OID> --query "{name:displayName, type:servicePrincipalType}" -o table
# RBAC grants on the vault
VAULT_ID=$(az keyvault show --name <VAULT> --query id -o tsv)
az role assignment list --scope "$VAULT_ID" -o table
# Access-policy grants on the vault
az keyvault show --name <VAULT> --query "properties.accessPolicies[].{oid:objectId, secrets:permissions.secrets}" -o table
# Network rules
az keyvault network-rule list --name <VAULT> -o jsonc
# Secret state (disabled / soft-deleted)
az keyvault secret show --vault-name <VAULT> --name <SECRET> --query "attributes.enabled" -o tsv
az keyvault secret list-deleted --vault-name <VAULT> -o table
# Confirm logged-in tenant matches the vault
az account show --query "{tenantId:tenantId, user:user.name}" -o table
# Grant access (pick the model)
az role assignment create --assignee <OID> --role "Key Vault Secrets User" --scope "$VAULT_ID"
az keyvault set-policy --name <VAULT> --object-id <OID> --secret-permissions get list
Conclusion
A Key Vault Forbidden means the caller authenticated but has no authorization to read the secret. The usual root causes:
- No access policy granting
secrets get(vault in access-policy mode). - No data-plane RBAC role like
Key Vault Secrets User(vault in RBAC mode). - The wrong identity was granted — user instead of the managed identity that actually calls.
- A firewall or private-endpoint network rule blocks the caller before the data plane.
- The secret is disabled or soft-deleted, so the version cannot be read.
- You authenticated against the wrong tenant, so the caller’s object ID does not exist in the vault’s directory.
Read the appid/oid/iss in the error first — it names the caller and tenant — then confirm the vault’s model and grant access to that exact identity.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.