Fix "access denied" on Azure, layer by layer

The triage for any Azure refusal — 401, 403, AuthorizationFailed, ForbiddenByRbac, RequestDisallowedByPolicy, AADSTS — in the order that finds the cause fastest: was a token issued, which identity and tenant does it carry, which plane refused (Resource Manager, a service's data plane, Microsoft Graph), is there a role at the right scope, is a deny assignment, policy, firewall or service permission model in the way, or has the change just not applied yet.

markdownguidev15KKJ-HPJRstage: operatetrust: officialservice: entraservice: rbacservice: policyinterface: anyAzure access denied403 Forbidden Azure401 Unauthorized AzureAuthorizationFailedForbiddenByRbacRequestDisallowedByPolicywhich identity is callingdecode access token oid tid audaz account showaz account get-access-tokencontrol plane vs data planedata role missingdeny assignmentAzure Policy denyservice firewall 403InvalidAuthenticationTokenTenantwrong tenantrole assignment not applied yetMicrosoft Graph Authorization_RequestDenied

Created 21 Oct 2026 · Modified 23 Oct 2026 · 1 version

Work outward from the token. Most wasted time comes from adding roles when the call was made by a different identity, in a different tenant, or refused by something that isn't RBAC at all. Each step below either finds the cause or rules a layer out.

1. Was a token issued at all?

An AADSTS… code, a CredentialUnavailableError or AggregateAuthenticationError means no token: nothing about roles matters yet.

  • AADSTS for an app or service principal → reference/entra/errors.md
  • AADSTS for a user (MFA, conditional access) → reference/entra/sign-in-errors.md
  • Federated (GitHub, Kubernetes) → reference/entra/federated-credential-errors.md
  • SDK credential messages → reference/azure-identity/errors.md

2. Which identity, which tenant?

az account show --query "{user:user.name, type:user.type, tenant:tenantId, sub:id}"
az account get-access-token --scope https://management.azure.com/.default --query accessToken -o tsv \
  | node -e "const p=require('fs').readFileSync(0,'utf8').split('.')[1]; const c=JSON.parse(Buffer.from(p,'base64url')); console.log({oid:c.oid, tid:c.tid, aud:c.aud, appid:c.appid ?? c.azp, roles:c.roles, scp:c.scp})"

In code, log the authenticated account (reference/azure-identity/credential-chain.md). Compare:

  • oid with the principal in your role assignment. A different oid means DefaultAzureCredential picked another credential, or a user-assigned identity wasn't selected.
  • tid with the resource's tenant. InvalidAuthenticationTokenTenant means the token is from another tenant: sign in with --tenant <id>.
  • aud with the service. A Resource Manager token can't call Key Vault's data plane, and vice versa.

The command prints the token's claims, not the token. Never paste a full token into a chat or a log.

3. Which plane refused?

You calledThe refusal looks likePermission comes from
Resource Manager (management.azure.com: create, configure, delete, az resource commands, Bicep)AuthorizationFailed naming an action like Microsoft.Storage/storageAccounts/writeAzure RBAC Actions
A service's data endpoint (*.vault.azure.net, *.blob.core.windows.net, *.servicebus.windows.net…)403 with a service code (ForbiddenByRbac, …)Azure RBAC DataActions, or the service's own model (Key Vault access policies, shared keys, SAS)
Microsoft Graph (create apps, read users)Authorization_RequestDenied / Insufficient privileges to complete the operationMicrosoft Entra roles or Graph application permissions with admin consent, not Azure RBAC

Owner and Contributor carry no data actions. "I'm Owner and still get 403 reading a secret" is the expected result: assign the data role.

4. Is there a role at the right scope?

az role assignment list --assignee <oid> --all --include-inherited --include-groups \
  --query "[].{role:roleDefinitionName, scope:scope}" -o table

The scope must be the resource or a parent of it. If the role is there but new, wait up to about 10 minutes and get a fresh token. For a managed identity whose access comes through an Entra group, expect hours (reference/managed-identity/errors.md). Errors from creating the assignment itself: reference/rbac/errors.md.

5. Something other than RBAC

LayerSignalCheck
Deny assignment (deployment stack deny settings, managed apps)403 despite a matching roleaz rest --method get --uri "https://management.azure.com/<scope>/providers/Microsoft.Authorization/denyAssignments?api-version=2022-04-01"
Azure PolicyRequestDisallowedByPolicy, or a deny/denyAction 403 naming a policyreference/policy/effects-and-errors.md
Resource lockWrites or deletes refused although the role allows them. ReadOnly acts like Reader; CanNotDelete blocks deletion onlyaz lock list --resource-group <rg>
Service firewall / private endpoint403 naming the client IP (Client address … is not authorized …)The service's network rules (reference/keyvault/access-and-errors.md; storage and networking families)
Service permission modelKey Vault in access-policy mode ignores RBAC data roles; storage accounts can refuse shared-key or SAS authaz keyvault show -n <kv> --query properties.enableRbacAuthorization

6. Still refused

Re-run the exact call with --debug (CLI) or SDK logging at info, keep the response's correlation or request ID, and compare the action named in the error with the role definition's Actions / DataActions (az role definition list --name "<role>" --query "[0].permissions"). A custom role missing one action is a common last cause.

Read this document as markdown — the same bytes an agent receives.