Azure Troubleshooting

Troubleshooting AuthorizationFailed: Reading Effective Azure RBAC Permissions

You run one command — az storage account create, a portal Save, a terraform apply — and Azure answers with a wall of red:

(AuthorizationFailed) The client 'a1b2c3d4-...' with object id 'a1b2c3d4-...'
does not have authorization to perform action 'Microsoft.Storage/storageAccounts/write'
over scope '/subscriptions/.../resourceGroups/rg-prod' or the scope is invalid.
If access was recently granted, please refresh your credentials.

This is the most common access error in Azure, and it is maddening precisely because it is honest. The message names the principal (who), the action (what), and the scope (where) — and that triple is the whole diagnosis. Azure RBAC (Role-Based Access Control) is the authorization system that decides, for every control-plane call to Azure Resource Manager (ARM), whether a given identity may perform a given action on a given resource. When it says AuthorizationFailed, it is not broken — it evaluated your effective permissions and found no role granting that action at that scope, or found a deny that overrides everything you were granted. The skill is reading the message correctly, then reading your effective permissions to confirm which of the two it is.

This is the playbook for that skill. We treat AuthorizationFailed not as one error but as a family of root causes — no assignment, wrong scope, propagation lag, a deny assignment from a managed app or Blueprint, a NotActions subtraction, a stale token that predates your new role, ABAC condition failure, a resource lock masquerading as a permissions error, and the control-plane-versus-data-plane trap where you have the role but the call needs a different one. For each you’ll learn the exact az role assignment list, az role definition, az provider operation and Check access path that confirms it, and the precise grant that fixes it. Because RBAC is layered (management group → subscription → resource group → resource), most of the work is figuring out where a permission is or isn’t — a reading exercise the tools make mechanical once you know which one to open.

By the end you will stop guessing and re-running with --debug. When the error fires you’ll parse the principal, action and scope in five seconds, decide whether you’re missing a grant or hitting a deny, run the one command that proves it, and assign the smallest role at the right scope — without handing out Owner and without waiting on a ticket. If you’re new to roles and scopes, read Azure RBAC Fundamentals: Roles, Scopes and Assignments Without the Confusion first; this article assumes that base and goes straight to the failure modes.

What problem this solves

RBAC is invisible until it says no. You build, you deploy, the assignment someone made months ago quietly covers you — until you touch a resource at a scope nobody granted, or a guardrail you didn’t know existed denies the write. Then the bare AuthorizationFailed string is all you get, and because it deliberately reveals nothing about what role would have worked, an engineer who can’t read it burns an hour. The information you need is real and queryable — Azure will tell you exactly which assignments the principal has and exactly which action the call required — but it lives across az role assignment list, the Check access / Effective access blade, az provider operation show, and the Activity log, and if you don’t know which one maps to which cause you click in circles.

What breaks without this knowledge is predictable and expensive. The on-call engineer escalates to a subscription Owner who “fixes” it by granting Owner at the subscription (now an over-privileged identity sits in production forever, an audit finding waiting to happen). A pipeline’s service principal fails a terraform apply and the team disables a policy, or worse, runs the deploy as a human admin, defeating the entire point of automation. A Contributor swears they have access yet still can’t grant a role to a managed identity — because granting roles needs Microsoft.Authorization/roleAssignments/write, which Contributor does not have, and nobody reads the role definition to discover that. Every one of these is a five-minute fix mis-diagnosed into an incident.

Who hits this hardest: CI/CD service principals and managed identities, whose permissions are set once and drift as the infra grows; newly onboarded engineers waiting on propagation lag; teams under landing zones, Blueprints or managed applications that introduce deny assignments the app team never sees; and anyone confusing control-plane (manage the resource) with data-plane (read the data inside) — the Storage Reader who sees the account but gets AuthorizationFailed listing blobs, because blob data needs Storage Blob Data Reader. The fix is almost never “give them Owner” — it’s “find the missing action or the deny, and grant the one role that supplies it at the narrowest scope.”

To frame the field before the deep dive, here is every root-cause class this article covers, the question it forces, and the first place to look:

Root-cause class What’s actually wrong First question to ask First place to look
No role assignment Principal has no role granting the action at any covering scope Does the principal have any assignment that covers this scope? az role assignment list --assignee <id> --all
Wrong scope Role exists but at a scope that doesn’t cover the target Is the assignment at/above the resource, or off to the side? Check access blade → the resource’s IAM
Propagation lag Assignment is correct but not yet effective Was the role granted in the last few minutes? Re-auth + wait; Activity log timestamp
Deny assignment A deny overrides the role you do have Is there a deny from a managed app / Blueprint / lock? az role assignment list --include-inherited + Deny assignments blade
NotActions subtraction The role excludes this specific action Does the role’s NotActions list the failing action? az role definition list --name <role>
Stale token Token was minted before the new role Did it work after you signed out and back in? az account get-access-token / re-login
Control vs data plane You have the management role, the call needs a data role Is this action on the resource or on the data inside it? The action string: .../write vs .../blobServices/.../read
ABAC condition fails A conditional assignment’s condition isn’t met Does the assignment carry a condition you don’t satisfy? az role assignment list --query "[].condition"
Resource lock A CannotDelete/ReadOnly lock blocks the write Is the error really about a lock, not a role? az lock list --resource-group <rg>

Learning objectives

By the end of this article you can:

Prerequisites & where this fits

You should already understand the RBAC mental model: a role assignment binds a security principal (a user, group, service principal, or managed identity, identified by an Entra object id) to a role definition (a named set of permitted actions) at a scope (a node in the Azure resource hierarchy). You should know how to run az in Cloud Shell, read JSON output, and that scope is hierarchical — a role granted at a resource group applies to every resource in it. Familiarity with Azure Resource Hierarchy Explained: Subscriptions, Resource Groups and Resources makes the scope reasoning automatic, and Build Custom RBAC Roles: Actions, NotActions, and DataActions for Least Privilege covers the action grammar this article reads constantly.

This sits in the Identity & Troubleshooting track. It assumes the fundamentals above and pairs tightly with three neighbours. Management Groups 101: Designing a Hierarchy That Scopes Policy and RBAC is where inherited assignments and many deny sources originate. Azure Policy Effects Decoded: Deny vs Audit vs Modify vs DeployIfNotExists explains the other “you can’t do that” — a Policy deny, which looks similar but is a different subsystem with a different fix. And Managed Identities Demystified: System vs User-Assigned and When to Use Each covers the identities that hit this error most in automation. For the data-plane variant on secrets, Key Vault RBAC vs Access Policies: Choosing and Migrating the Permission Model is the canonical case.

A quick map of who or what owns each layer of an authorization decision, so you escalate to the right place fast:

Layer What lives here Who usually owns it Failure classes it can cause
Entra ID (identity) The principal, its object id, group membership Identity team Wrong principal, guest gaps, group not yet effective
Token (AAD) The signed claims the call presents Azure CLI / the runtime Stale token predating a new role assignment
Role assignment Principal ↔ role ↔ scope binding Resource/sub Owner or User Access Admin No assignment, wrong scope, propagation lag
Role definition The Actions/NotActions the role permits Microsoft (built-in) or platform team (custom) NotActions subtraction; wrong role chosen
Deny assignment An explicit deny that overrides allows Managed app / Blueprint / system Hard block even with a granting role
Resource lock CannotDelete / ReadOnly on the resource Resource owner Write/delete blocked, looks like a role issue
Azure Policy deny effect on the request body Governance team Request blocked at admission, not by RBAC

Core concepts

Five mental models make every later diagnosis obvious.

The error names what the engine checked, not where your access “should” come from. Every control-plane call carries a principal (an Entra object id) and targets a scope (a resource id). ARM asks: across all role assignments this principal has at this scope and every scope above it, is there one whose Actions include the requested action — with no NotActions excluding it and no deny assignment forbidding it? If yes, allow; otherwise AuthorizationFailed. The message hands you all three inputs, so your entire job is to reproduce that evaluation by reading the principal’s effective permissions.

Allow is additive and inherited; deny is absolute. Grants flow downward — a role at a management group applies to every subscription, resource group and resource beneath it; a role at a resource group, to every resource in it. You accumulate the union of every assignment at the target scope and all ancestors — no ordering, no precedence. The one thing that breaks the additive model is a deny assignment: an explicit “this principal may NOT perform this action here,” which overrides any allow no matter how high the granting role sits. You can’t create deny assignments in the portal — they come from Azure managed applications, Blueprints (deprecated but still present), and a few system protections. Have a granting role and still get denied? A deny assignment is the prime suspect.

A role is Actions minus NotActions, on two planes. A role definition is four lists. Actions are the control-plane operations it permits. NotActions are operations subtractedContributor has * in Actions but lists Microsoft.Authorization/*/Write, Microsoft.Authorization/*/Delete and Microsoft.Authorization/elevateAccess/Action in NotActions, which is exactly why a Contributor can do almost anything except grant roles. DataActions/NotDataActions are the same idea for the data plane — operations on the data inside a resource — kept separate so you can manage a storage account without reading its data, or read the data without managing the account. Crucially, NotActions are subtractions, not denies: another role can add the action back. Only a deny assignment is absolute.

Scope is a path, and “covers” means ancestor-or-self. Scopes are slash-delimited ids forming a tree: managementGroups/<mg>/subscriptions/<sub>.../resourceGroups/<rg>.../providers/<rp>/<type>/<name>. An assignment covers a target if its scope is the target or an ancestor of it. A role at rg-prod covers a VM inside it; the same role at rg-staging covers nothing in rg-prod — sibling, not ancestor. The single most common “but I have access!” is a real assignment at the wrong scope. The other half of “or the scope is invalid” is a literal malformed/typo’d id.

Control plane and data plane are different doors with different keys. ARM (management.azure.com) is the control plane — create/configure/delete the resource. The resource’s own endpoint (blob.core.windows.net, vault.azure.net) is the data plane — read/write the contents. They authorize separately: Storage Account Contributor manages the account but grants no blob data access; a Reader on an RBAC-mode Key Vault sees the vault but can’t read a secret value until granted Key Vault Secrets User. When the failing action contains a data path (/blobServices/, /secrets/, /queueServices/), you need a data role — management roles won’t help no matter how broad.

The vocabulary in one table

Before the deep sections, pin down every moving part. The glossary repeats these for lookup; this is the mental model side by side:

Concept One-line definition Where it lives Why it matters to AuthorizationFailed
Security principal User, group, SPN, or managed identity (an object id) Entra ID The “who”; the error’s object id
Role definition Named set of allowed actions Subscription / tenant The Actions/NotActions evaluated
Role assignment Principal ↔ role ↔ scope binding At a scope The grant that’s present or missing
Scope A node in the resource hierarchy MG / sub / RG / resource Must be ancestor-or-self of the target
Action One operable verb on a resource type Inside the role The exact thing the call needed
NotActions Actions subtracted from a role Inside the role Why Contributor can’t assign roles
DataActions Data-plane operations a role permits Inside the role Why a Reader can’t read blob data
Deny assignment Explicit deny overriding allows Managed app / Blueprint / system Blocks even a granted role
Propagation Time for an assignment to take effect Platform behaviour A correct grant fails for minutes
Effective access The union of all assignments at a scope Portal blade / az query What you read to reproduce the decision
Control plane Manage the resource via ARM management.azure.com Needs Actions
Data plane Read/write the data inside The resource’s own endpoint Needs DataActions

Reading the AuthorizationFailed message

The message is a structured record, not a wall of noise. Learn to read its fields and you have your hypothesis before you run anything. The canonical shape:

(AuthorizationFailed) The client '<upn-or-appId>' with object id '<objectId>'
does not have authorization to perform action '<provider/type/operation>'
over scope '<resourceId>' or the scope is invalid.
If access was recently granted, please refresh your credentials.

Three fields carry the entire diagnosis. Read them in this order:

Field in the message What it tells you The hypothesis it points to Confirm it with
object id '<objectId>' The exact principal ARM evaluated Are you even running as who you think? (SPN vs user vs MI) az ad signed-in-user show / az account show
action '<...>' The exact operation required Which role supplies this action, on which plane az provider operation show --namespace <ns>
over scope '<...>' The exact target scope Is there a covering assignment at/above it az role assignment list --scope <id> --include-inherited
“the scope is invalid” The id may be malformed Typo / wrong subscription / deleted resource Echo the id; az resource show --ids <id>
“recently granted… refresh” Propagation/token hint Stale token or not-yet-propagated assignment Re-login; wait; recheck

The two halves of “or the scope is invalid” matter. If the scope id is well-formed and the resource exists, treat it as a permissions problem and go read effective access. If the id is malformed (a missing segment, a wrong subscription guid, a resource you already deleted), it is a scope problem — fix the id, no role change needed. Echo the scope and eyeball it before assuming RBAC.

The action string also tells you which plane and which role family you need. Mapping the action shape to the fix:

Action string contains… It’s a… Role family to look in Example role
.../write, .../delete, .../action on the resource type Control-plane management op Built-in management roles Contributor, Storage Account Contributor
Microsoft.Authorization/roleAssignments/write A grant operation Access-management roles only Owner, User Access Administrator, RBAC Administrator
.../blobServices/.../read, /queueServices/, /tableServices/ Data-plane storage op Storage Data roles Storage Blob Data Reader/Contributor
Microsoft.KeyVault/vaults/secrets/... (data) Data-plane Key Vault op Key Vault data roles (RBAC mode) Key Vault Secrets User
.../read only, and the call is a list/show A read op Any role with */read Reader
Microsoft.Authorization/elevateAccess/Action The break-glass elevation Reserved (Global Admin path) n/a (special)

The closing line — “If access was recently granted, please refresh your credentials” — is a genuine hint, not boilerplate: it flags the two time-dependent causes (a stale token or propagation lag). If the assignment exists when you check but the call still failed, that sentence is usually the answer.

Reading effective permissions: the core skill

Everything downstream is one of two readings — the principal’s assignments, or a scope’s effective access. Master both commands and the portal equivalents and you can confirm any cause in under a minute.

List a principal’s assignments

Start from the principal in the error. --all searches every scope (not just the current one); --include-inherited pulls in assignments from ancestor scopes that flow down:

# Replace with the object id from the error message
PRINCIPAL=a1b2c3d4-1111-2222-3333-444455556666

# Every assignment this principal has, at any scope, including inherited
az role assignment list --assignee "$PRINCIPAL" --all --include-inherited \
  --query "[].{role:roleDefinitionName, scope:scope, type:principalType}" -o table

Read the output against the failing scope: is there a row whose scope is the target or an ancestor of it, whose role plausibly contains the failing action? If no row covers the scope → no assignment / wrong scope. If a row covers it but the role shouldn’t supply that action → check the role definition (NotActions, or wrong plane).

Confirm you’re running as who you think first — the object id in the error is authoritative, and SPNs, managed identities and your user are different objects:

az ad signed-in-user show --query "{upn:userPrincipalName, id:id}" -o table
az account show --query "{sub:name, subId:id, tenant:tenantId, user:user.name}" -o table
# If the error names an app/SPN, resolve its object id:
az ad sp show --id <appId-or-objectId> --query "{name:displayName, objId:id}" -o table

A frequent shock: the failing principal is a service principal or managed identity — a pipeline or a Function’s identity — so checking your own access proves nothing. Always diagnose the principal named in the error.

Read a scope’s effective access (Check access)

The other direction: stand at the target resource and ask “does this principal cover it?” In the portal that’s Resource → Access control (IAM) → Check access → (pick the principal), showing every assignment that applies there, including inherited ones with their source scope. The CLI equivalent, scoped to the resource:

SCOPE="/subscriptions/<sub>/resourceGroups/rg-prod/providers/Microsoft.Storage/storageAccounts/stprod001"

# Every assignment effective at this scope (inherited included), filtered to the principal
az role assignment list --scope "$SCOPE" --include-inherited --assignee "$PRINCIPAL" \
  --query "[].{role:roleDefinitionName, scope:scope}" -o table

The scope column is the punchline: it tells you where each effective assignment is anchored. A row anchored at the subscription means the grant is inherited from above; an empty result means nothing covers this principal here — the missing-grant case. The portal’s Effective access / Check access view renders the same union with a friendlier source-scope label.

Confirm what action a role actually contains

When a role should cover the action but the call still failed, read the role definition. This is where NotActions subtractions and plane mismatches reveal themselves:

# Dump a role's four action lists
az role definition list --name "Contributor" \
  --query "[0].permissions[0].{actions:actions, notActions:notActions, dataActions:dataActions, notDataActions:notDataActions}" -o json

For Contributor this prints actions: ["*"] and notActions: ["Microsoft.Authorization/*/Write", "Microsoft.Authorization/*/Delete", "Microsoft.Authorization/elevateAccess/Action", "Microsoft.Blueprint/blueprintAssignments/write", "Microsoft.Blueprint/blueprintAssignments/delete", "Microsoft.Compute/galleries/share/action"] — proof that “everything except granting roles (and a few specifics)” is literal. Map your failing action against the notActions list: if it’s there, this role will never authorise it and you need a different role that supplies it.

Confirm the action exists and which plane it’s on

To learn whether an action is control-plane or data-plane, and which built-in roles include it, query the provider’s operation catalogue:

# All operations for a provider, with their display text
az provider operation show --namespace Microsoft.Storage \
  --query "resourceTypes[?name=='storageAccounts/blobServices/containers/blobs'].operations[].{op:name, display:displayName, isData:isDataAction}" -o table

The isDataAction flag is the tell: true means the operation lives on the data plane and is authorised by DataActions (so you need a Data role), not management Actions. This single query resolves most control-vs-data confusion. To recap the four readings: query by principal (--assignee ... --all --include-inherited) for all its grants; by scope (--scope ... --include-inherited, or Check access) for what covers a target; by role (az role definition list) for what actions it permits or excludes; and by action (az provider operation show) for whether it’s data-plane and which roles carry it.

The deny assignment trap

If the principal has a granting role at a covering scope and the call still returns AuthorizationFailed, you’re almost certainly looking at a deny assignment — an explicit “may NOT perform this action” that overrides every allow. It’s the most confusing RBAC failure because the obvious check (does it have a role?) comes back yes. Deny assignments aren’t created through Add role assignment; they’re emitted by a small set of system features:

Deny-assignment source Why it exists How to recognise it Can you remove it directly?
Azure managed application Publisher locks the managed resource group so consumers can’t tamper Deny on a *-managed-rg; principals = everyone except the app’s identity No — managed by the app; delete the managed app to remove
Azure Blueprints (deprecated) Blueprint assignment with denyAssignment locking artifacts Deny tied to a Blueprint assignment Remove/loosen the Blueprint assignment
System (platform) Built-in protections on certain platform-managed resources System-principal deny No — platform-owned

Read deny assignments in their own surface — Resource → Access control (IAM) → Deny assignments lists every deny touching the scope, the principals it applies to, and the actions it denies (its own NotActions are the exceptions — actions NOT denied). If your failing action is denied and your principal is in scope (and not excluded), that’s your answer. The crucial reframe: you don’t “grant around” a deny — adding a higher role does nothing. Address the source (use the managed app’s supported interface; loosen a Blueprint’s locking). Allow vs deny, made explicit:

Property Allow (role assignment) Deny (deny assignment)
Created via Add role assignment (you, an Owner) Managed app / Blueprint / system only
Combine rule Additive union across scopes Overrides all allows at/above scope
Removable by you Yes, if you have access-mgmt rights Usually no — address the source
Shows up in IAM → Role assignments IAM → Deny assignments
Fix when it bites Grant the right role/scope Use the supported interface / remove the source

NotActions, scope and the “I’m Contributor, why can’t I?” cases

Two failures dominate the “but I clearly have rights” complaints, and both are reading problems, not missing-grant problems.

NotActions subtraction — the Contributor-can’t-grant-roles case

A role with Actions: ["*"] looks all-powerful until you read its NotActions. Contributor is deliberately “manage everything except authorization,” so holders can’t escalate by granting themselves Owner. This trips up automation constantly — a pipeline running as Contributor tries to assign a role to a managed identity (a normal IaC step) and gets:

(AuthorizationFailed) ... does not have authorization to perform action
'Microsoft.Authorization/roleAssignments/write' over scope '/subscriptions/.../resourceGroups/rg-app'

The action is Microsoft.Authorization/roleAssignments/write, which sits squarely in Contributor’s NotActions. Confirm and fix by choosing a role that does supply it — User Access Administrator (or the newer Role Based Access Control Administrator, which is more constrained), scoped narrowly:

# Confirm Contributor excludes the action
az role definition list --name "Contributor" --query "[0].permissions[0].notActions" -o json
# -> includes "Microsoft.Authorization/*/Write" -> roleAssignments/write is excluded

# Grant the pipeline the ability to manage role assignments at the RG only (least privilege)
az role assignment create --assignee "$PRINCIPAL" \
  --role "Role Based Access Control Administrator" \
  --scope "/subscriptions/<sub>/resourceGroups/rg-app"

The common built-in roles that can assign roles, and how they differ:

Role Can assign roles? Constraint When to use
Owner Yes — any role Full control of everything else too Avoid for automation; too broad
User Access Administrator Yes — any role Manages access but not resources Classic “let this principal grant roles”
Role Based Access Control Administrator Yes, but constrained Can be limited to specific roles via condition; cannot assign privileged roles to itself freely Preferred least-privilege grant for pipelines
Contributor No Microsoft.Authorization/*/Write in NotActions Manage resources, never grant roles

Wrong scope — the right role in the wrong place

The other dominant case: the principal genuinely has the role, but at a scope that doesn’t cover the target. A Contributor on rg-staging cannot touch rg-prod; an Owner on Subscription A cannot act in Subscription B. Because the assignment exists, az role assignment list --assignee shows a reassuring row — you have to check the scope column against the failing scope.

# Does ANY of the principal's assignments cover this exact scope (or an ancestor)?
TARGET="/subscriptions/<subB>/resourceGroups/rg-prod/providers/Microsoft.Web/sites/app-prod"
az role assignment list --assignee "$PRINCIPAL" --all --include-inherited \
  --query "[?contains('$TARGET', scope)].{role:roleDefinitionName, scope:scope}" -o table
# Empty -> nothing covers the target. Non-empty -> something does; look elsewhere (NotActions/plane/deny).

The fix is to assign at the smallest scope that covers the target — and scope choice is the least-privilege decision. Default to the single resource (tightest) or resource group (the sensible default when a principal owns a workload’s RG); reserve the subscription for platform/landing-zone operators and audit it; use the management group only for genuine org-wide roles, since a grant there is inherited by every subscription beneath it.

Control plane vs data plane

The trap that eats the most senior-engineer hours: you have a perfectly good management role and the operation needs a data role. The two planes authorise independently — ARM (management.azure.com) authorises control-plane actions via a role’s Actions, while the resource’s own endpoint (blob.core.windows.net, vault.azure.net) authorises data-plane operations via DataActions. Management roles like Contributor and Storage Account Contributor carry no DataActions, so they grant zero data access by design — exactly what you want for an operator who should run a resource without reading its contents.

The storage case is canonical: listing blobs “as the account” works only with a blob data role; otherwise it’s AuthorizationFailed/403 even as the account’s Owner (unless account-key access papers over it, an anti-pattern). The fix is a data role at the account/container scope:

# Symptom: can see the account, can't list blobs. Confirm the action is data-plane:
az provider operation show --namespace Microsoft.Storage \
  --query "resourceTypes[?contains(name,'blobServices/containers/blobs')].operations[?isDataAction].name" -o tsv | head

# Grant the data-plane role at the account scope (container scope for tighter control)
az role assignment create --assignee "$PRINCIPAL" \
  --role "Storage Blob Data Reader" \
  --scope "/subscriptions/<sub>/resourceGroups/rg-prod/providers/Microsoft.Storage/storageAccounts/stprod001"

The same split shows up across services. The management role manages the resource; the data role touches the contents:

Service Control-plane role (manage the resource) Data-plane role (touch the data) The error when you confuse them
Storage (Blob) Storage Account Contributor Storage Blob Data Reader / Contributor Can’t list/read blobs despite owning the account
Key Vault (RBAC mode) Contributor / Key Vault Contributor Key Vault Secrets User / Crypto User Can see the vault, can’t read a secret value
Cosmos DB DocumentDB Account Contributor Cosmos DB Built-in Data Reader/Contributor (data-plane RBAC) Can manage the account, can’t read items
Service Bus Contributor / Azure Service Bus Data Owner (mgmt parts) Azure Service Bus Data Sender / Receiver Can manage the namespace, can’t send/receive
Storage (Queue) Storage Account Contributor Storage Queue Data Contributor Can’t read queue messages

Two rules make this less mysterious. Read the action string — a data path (/blobServices/, /secrets/, an isDataAction: true op) means a data role, full stop. And for Key Vault, confirm the vault is in RBAC mode: if it’s on legacy access policies, RBAC roles are ignored entirely and you grant via access policies instead — the whole subject of Key Vault RBAC vs Access Policies: Choosing and Migrating the Permission Model.

Propagation, tokens and ABAC conditions

Three time- and condition-dependent causes make a correct assignment look broken. Recognise them so you don’t “fix” something that was already right.

Propagation lag

A new role assignment is not instant — ARM replicates it and caches refresh, typically within a couple of minutes but longer across regions or for group-based grants (where two propagations stack: group membership, then the assignment). The error says so: “If access was recently granted, please refresh your credentials.”

# Verify the assignment actually exists now (separate "does it exist" from "is it effective")
az role assignment list --assignee "$PRINCIPAL" --scope "$SCOPE" \
  --query "[].{role:roleDefinitionName, created:createdOn}" -o table

If the assignment shows up with a recent createdOn, it exists — the failure is propagation/token, not a missing grant. Wait, re-authenticate, and retry. Do not add a second, broader role to “force” it; you’ll forget to remove it.

Stale token

A bearer token carries the principal’s claims as of when it was issued and is valid for its lifetime (commonly up to an hour). A token minted before the new assignment (or before the group membership that confers it) doesn’t carry the new access until refreshed — even though the assignment is live server-side. This is why “log out and back in” genuinely fixes things, and the same holds for SPNs and managed identities (their cached token must refresh):

# Force a fresh token so new assignments / group membership are reflected
az logout && az login
# In a pipeline, re-run the SPN login step to re-acquire the token.

Group membership changes are the worst offender — only reflected in a newly issued token, so a long-lived session keeps using old claims until it renews.

ABAC conditions

ABAC (Attribute-Based Access Control) lets a role assignment carry a condition — extra constraints (e.g. “only blobs tagged project=x”). If a conditional assignment is the only thing granting your action and the condition isn’t satisfied, you get AuthorizationFailed even though the role and scope are right. Check for conditions:

az role assignment list --assignee "$PRINCIPAL" --all \
  --query "[?condition != null].{role:roleDefinitionName, scope:scope, condition:condition}" -o json

If a condition is present, read it: your request must match its attributes (resource tags, blob path, request attributes) to be allowed. The fix is to satisfy the condition (act on a matching resource) or, if the condition is wrong, edit it. The three time/condition causes side by side:

Cause Tell-tale Confirm Fix (do NOT broaden the role)
Propagation lag Just granted; assignment exists but call fails createdOn is minutes ago Wait, re-auth, retry
Stale token Worked after sign-out/in; long session Token issued before the grant az logout && az login / refresh SPN token
Group membership not effective Granted via a group you just joined Membership recent; old token in use New token (re-login) reflects membership
ABAC condition fails Assignment carries a condition --query "[?condition != null]" Match the condition or edit it

Architecture at a glance

There is no diagram to study here — hold this mental model instead, because the shape of an authorization decision is what makes the playbook obvious. Picture the request as a single straight line with one gate on it. Your call leaves a principal (an Entra object id) carrying a token of signed claims and arrives at Azure Resource Manager addressed to a scope asking for an action. ARM is the gate, evaluating one question: across every role assignment this principal holds at the target scope and every ancestor above it — resource → resource group → subscription → management group — does the union of their Actions include the requested action (minus any NotActions), with no deny assignment forbidding it and any ABAC condition satisfied? Allow flows down and accumulates; deny sits on top and overrides.

So every AuthorizationFailed is one of two failures at that gate: the union of allows didn’t contain the action at a covering scope (missing grant, wrong scope, NotActions subtraction, wrong plane, or a not-yet-effective grant), or a deny above the allows vetoed it. Your diagnosis walks the line in ARM’s own order: confirm which principal the token represents, walk scopes from the resource upward for a covering allow, check for an overriding deny, then — if an allow exists and no deny does — suspect time (propagation/stale token) or plane (control vs data). The whole method is “reproduce ARM’s evaluation by reading effective access from the resource upward.”

Real-world scenario

Vantix Logistics runs a fleet-telemetry platform on Azure: a set of Functions and an AKS workload writing to Cosmos DB and Blob Storage, deployed entirely by a Terraform pipeline running as a single service principal (sp-vantix-cicd) in Central India. The platform team is five engineers; everything ships through CI under a landing zone the central cloud team manages, with subscriptions sitting under a mg-workloads management group.

The incident began on a Tuesday release. The pipeline had been green for months, then a routine terraform apply — adding a new storage account and granting the Functions’ managed identity a data role on it — failed at the role-assignment step:

(AuthorizationFailed) The client 'sp-vantix-cicd' with object id 'b7...'
does not have authorization to perform action 'Microsoft.Authorization/roleAssignments/write'
over scope '/subscriptions/.../resourceGroups/rg-telemetry-prod'.

The on-call engineer assumed a propagation blip and re-ran — same failure — then suspected the SPN had lost permissions. A check said otherwise: az role assignment list --assignee b7... --all showed sp-vantix-cicd holding Contributor at the subscription, exactly as designed. So it had a role, yet the call failed — the shape that screams deny assignment but here meant something simpler.

The breakthrough came from reading the action, not the role. The failing action was Microsoft.Authorization/roleAssignments/write; dumping az role definition list --name Contributor --query "[0].permissions[0].notActions" showed Microsoft.Authorization/*/Write. Contributor intentionally cannot grant roles. Every prior pipeline run only created resources; the new Terraform that wired the Functions’ managed identity to a Storage data role was the first time the SPN tried to write a role assignment — and Contributor said no. The gap had been invisible for months.

The team’s first instinct was to ask for Owner at the subscription — quick, unblocks the release. The lead pushed back: Owner on a CI principal is a standing escalation path and an audit finding. Instead they granted the narrower Role Based Access Control Administrator at the resource-group scope only, with a condition limiting it to the storage data roles the pipeline needed — supplying exactly roleAssignments/write where required and nowhere else. The re-run then hit a third issue: the managed identity could write to the account but not read blobs, because the pipeline had granted Storage Account Contributor (control plane) instead of Storage Blob Data Contributor (data plane). Correcting the Terraform cleared it; the release shipped ninety minutes after the first failure. The retro put three lines on the wall — “Read the action, not the role.” “Contributor never grants roles.” “Account roles aren’t data roles.” — and added a pipeline pre-flight that asserts the SPN holds RBAC-admin at the workload RG before any role-assignment step, so the next new workload fails fast instead of mid-release.

The incident as a timeline, because the order of moves is the lesson:

Time Symptom Action taken Effect What it should have been
10:02 roleAssignments/write AuthorizationFailed Re-run the pipeline Same failure Read the action string first
10:08 Still failing Check SPN’s assignments Contributor at sub — “it has access!” Don’t stop at “role exists”
10:20 Confusion (has role, still denied) Dump Contributor notActions Authorization/*/Write excluded — the answer This is the breakthrough
10:35 Root cause found Proposed: Owner at subscription Would work, over-privileged Don’t grant Owner to CI
10:45 Mitigated RBAC Admin at RG (conditioned) roleAssignments/write supplied narrowly Correct least-privilege fix
11:05 Re-run hits blob read failure Granted Storage Account Contributor Still can’t read blobs Needed a data role
11:25 Fixed Switch to Storage Blob Data Contributor Release ships Account role ≠ data role

Advantages and disadvantages

The “deny-by-default, additive-allow, hierarchical-scope” model both causes this error and makes it diagnosable. Weigh it honestly:

Advantages (why the model helps you) Disadvantages (why it bites)
The error names the exact principal, action and scope — the diagnosis is handed to you The bare string reveals nothing about which role would work; you must read effective access
Allow is additive and inherited — grant once high up, it flows down to everything beneath Inheritance hides where a grant comes from; “I have access” can be an assignment three scopes up
Least privilege is expressible precisely — narrow scope + specific role + ABAC condition Precision means more ways to be just short — wrong scope, NotActions, data vs control plane
Deny assignments give hard, un-escalatable guardrails for managed apps and platforms A deny is invisible in the normal IAM list and can’t be granted around — confusing when you have a role
Control/data-plane separation lets you manage a resource without reading its data The same separation surprises people: account Owner who can’t read a blob
Reader/Contributor/Owner plus hundreds of granular built-ins cover most needs Picking the right built-in among hundreds is itself a skill; the obvious one is often too broad
Propagation and token caching are normal distributed-system behaviour A correct grant fails for minutes; the time-based causes mimic a real permission gap

It bites hardest on CI/CD principals (permissions drift; the Contributor-can’t-grant-roles wall), data-plane workloads (the storage/Key Vault/Cosmos data-role surprise), and anyone under a landing zone where deny assignments and inherited grants originate out of view. Every disadvantage is manageable — but only if you know it exists.

Hands-on lab

Reproduce a real AuthorizationFailed, read effective permissions to localise it, and fix it with the narrowest role — all free (role assignments and reads cost nothing; we create one cheap storage account and delete it). Run in Cloud Shell (Bash). You’ll need rights to create a resource group, a storage account, and role assignments (Owner or User Access Administrator on a sandbox subscription).

Step 1 — Variables and a resource group.

RG=rg-rbac-lab
LOC=centralindia
ST=strbaclab$RANDOM   # globally-unique, lowercase, no dashes
SUB=$(az account show --query id -o tsv)
az group create -n $RG -l $LOC -o table

Step 2 — Create a storage account and a test service principal (our “victim” principal).

az storage account create -n $ST -g $RG -l $LOC --sku Standard_LRS -o table

# A throwaway SPN with NO role yet — this is the principal we'll diagnose
APPID=$(az ad sp create-for-rbac -n sp-rbac-lab --skip-assignment --query appId -o tsv)
OBJID=$(az ad sp show --id $APPID --query id -o tsv)
echo "SPN appId=$APPID objectId=$OBJID"

Expected: an account row, and an SPN created without any assignment (--skip-assignment).

Step 3 — Reproduce the failure: confirm the SPN has nothing. List the principal’s assignments — it should be empty:

az role assignment list --assignee $OBJID --all --include-inherited \
  --query "[].{role:roleDefinitionName, scope:scope}" -o table
# Expected: empty. With zero assignments, ANY action by this SPN -> AuthorizationFailed.

That’s the “no role assignment” root cause in its purest form — a principal with no covering grant.

Step 4 — Grant the WRONG thing on purpose (control plane), then prove the data-plane gap. Give the SPN Reader on the account, then check what blob data it can do:

az role assignment create --assignee $OBJID --role "Reader" \
  --scope "/subscriptions/$SUB/resourceGroups/$RG/providers/Microsoft.Storage/storageAccounts/$ST"

# Reader covers control-plane reads, but these blob DATA actions are NOT in it ->
# reading blob data would still AuthorizationFailed/403. Right plane is everything.
az provider operation show --namespace Microsoft.Storage \
  --query "resourceTypes[?contains(name,'blobServices/containers/blobs')].operations[?isDataAction].name" -o tsv | head -3

Step 5 — Fix it with the narrowest correct role. Grant the data-plane role at the account scope:

az role assignment create --assignee $OBJID --role "Storage Blob Data Reader" \
  --scope "/subscriptions/$SUB/resourceGroups/$RG/providers/Microsoft.Storage/storageAccounts/$ST"

# Confirm BOTH roles now cover the account: Reader + Storage Blob Data Reader.
az role assignment list --assignee $OBJID --all --include-inherited \
  --query "[].{role:roleDefinitionName, scope:scope}" -o table

The SPN can now read the account and its blob data, with no broader grant.

Step 6 — Read it the way you would in an incident (Check access). Reproduce the portal’s “who covers this scope?” reading:

SCOPE="/subscriptions/$SUB/resourceGroups/$RG/providers/Microsoft.Storage/storageAccounts/$ST"
az role assignment list --scope "$SCOPE" --include-inherited --assignee $OBJID \
  --query "[].{role:roleDefinitionName, anchoredAt:scope}" -o table
# anchoredAt shows the account scope — exactly what you'd read mid-incident.

Validation checklist. You reproduced AuthorizationFailed from a zero-grant principal, saw a control-plane role fail to supply data-plane access, fixed it with the narrowest data role at the resource scope, and read effective access both by-principal and by-scope. No Owner, no subscription-wide grant. The steps mapped to what each proves:

Step What you did What it proves Real-world analogue
3 List a zero-grant SPN “No assignment” → AuthorizationFailed A brand-new CI principal
4 Reader on the account, check data ops Control-plane role ≠ data access The storage blob-read surprise
5 Add Storage Blob Data Reader The narrow data role is the fix The actual least-privilege grant
6 Read effective access by scope How to confirm coverage mid-incident The 60-second diagnosis

Cleanup.

az role assignment delete --assignee $OBJID --scope "/subscriptions/$SUB/resourceGroups/$RG" 2>/dev/null
az ad sp delete --id $APPID
az group delete -n $RG --yes --no-wait

Cost note. Role assignments and IAM reads are free. The Standard_LRS account holds no data here, so it costs effectively nothing for the lab’s lifetime; deleting the resource group and SPN removes everything.

Common mistakes & troubleshooting

This is the playbook — the part you bookmark. First as a scannable table you read the moment the error fires, then the same entries with the full confirm-command detail underneath. It spans the basic gaps (no grant, wrong scope, stale token) and the advanced ones (deny assignments, NotActions, ABAC, cross-tenant, plane confusion).

# Symptom Root cause Confirm (exact cmd / portal path) Fix
1 AuthorizationFailed and the principal has no assignment covering the scope No role assignment at/above the target az role assignment list --assignee <id> --all --include-inherited → empty for the scope Assign the least-privilege role at the narrowest covering scope
2 Principal has the role but still denied; assignment is at a sibling scope Right role, wrong scope (sibling, not ancestor) Check the scope column vs the failing scope; Check access blade Re-assign at the resource/RG that actually covers the target
3 Just granted; assignment exists but call still fails Propagation lag / stale token createdOn is minutes ago; sign out/in fixes it Wait, re-authenticate, retry — don’t add a broader role
4 Has a granting role, yet still AuthorizationFailed Deny assignment overrides the allow IAM → Deny assignments; managed-RG / Blueprint deny Use the supported interface; remove/loosen the source (can’t grant around it)
5 Contributor can’t assign a role to a managed identity roleAssignments/write is in Contributor’s NotActions az role definition list --name Contributor --query "[0].permissions[0].notActions" Grant RBAC Administrator / User Access Admin at the RG
6 Can see a storage account but can’t list/read blobs Data-plane action needs a data role, not a mgmt role Action contains /blobServices/; isDataAction:true Assign Storage Blob Data Reader/Contributor at account/container
7 Reader on a Key Vault (RBAC mode) can’t read a secret Secret read is a data-plane op Action .../vaults/secrets/...; vault in RBAC mode Grant Key Vault Secrets User; verify vault uses RBAC not access policies
8 Worked yesterday, fails today after a group change Group membership only in a new token Membership added recently; long-lived session az logout && az login (new token reflects the group)
9 Error says “or the scope is invalid” and the id looks odd Malformed/typo’d scope, wrong sub, deleted resource Echo the id; az resource show --ids <id> Fix the resource id — not a role problem
10 Granted at a role that should work, action still missing Chose a role whose Actions lack the op (or NotActions excludes it) az provider operation show; az role definition list --name <role> Pick a role that supplies the exact failing action
11 Conditional assignment present; some requests denied ABAC condition not satisfied by the request az role assignment list --query "[?condition!=null]" Match the condition (tags/path) or edit it
12 Guest/partner user denied across tenants Cross-tenant identity not assigned in this tenant Confirm the user is a guest here; check object id Invite/assign the guest in this tenant, or use the right identity
13 Write/delete fails with a permissions-flavoured error, but role is fine A resource lock (ReadOnly/CannotDelete), not RBAC az lock list --resource-group <rg> / on the resource Remove/override the lock; or expect read-only
14 ARM rejects the create even though RBAC allows it Azure Policy deny on the request body (not RBAC) Activity log shows a policy denial with the policy name Fix the request to comply, or get a policy exemption
15 elevateAccess or “can’t manage other admins” Trying a reserved/escalation action Action is Microsoft.Authorization/elevateAccess/Action Use the proper break-glass/Global-Admin path; not a normal grant

The table is the playbook; the entries below add the reasoning for the four that cause the most wasted hours — the ones where the obvious check misleads you.

4. The principal has a granting role yet is still denied (deny assignment). This is the most confusing case because the obvious check — “does it have a role?” — comes back yes. A deny assignment overrides the allow, and it comes from an Azure managed application locking its managed resource group, a Blueprint assignment, or a system protection — never the normal Add-role-assignment flow, which is why it’s invisible in the usual IAM list. Confirm at Resource → Access control (IAM) → Deny assignments: a deny whose actions include yours and whose principals include yours (and don’t exclude you). You cannot grant around it — adding a higher role does nothing. Act through the managed app’s supported interface, or remove/loosen the source.

5. A Contributor pipeline can’t assign a role (NotActions). Microsoft.Authorization/roleAssignments/write sits in Contributor’s NotActions by design, so a Contributor can’t escalate by granting itself Owner. Any IaC step that wires a role to a managed identity hits this the first time it runs. Confirm with az role definition list --name Contributor --query "[0].permissions[0].notActions" (it shows Microsoft.Authorization/*/Write). Fix by granting Role Based Access Control Administrator (preferred — condition-constrainable) or User Access Administrator at the workload resource group — not Owner, not at the subscription.

6 & 7. Can see the resource, can’t touch its data (control vs data plane). Blob and secret operations are data-plane (DataActions); management roles like Storage Account Contributor, Key Vault Contributor, even Owner, carry none — so you can manage the resource and still get AuthorizationFailed/403 on its contents. Confirm by the action string (/blobServices/, /vaults/secrets/..., or isDataAction: true via az provider operation show). Fix with a data role: Storage Blob Data Reader/Contributor or Key Vault Secrets User, scoped to the account/vault (or container/secret). For Key Vault, first confirm the vault is in RBAC mode — on legacy access policies, RBAC roles are ignored and you grant via access policies instead.

3 & 8. A correct grant that “isn’t working” (propagation / stale token). A just-made assignment is live server-side but not yet in caches or in the token the call presented — the error’s “recently granted… refresh” line points right at it. The worst variant is group-based: a membership change is reflected only in a newly issued token, so a long-lived session keeps using old claims. Confirm the assignment shows a recent createdOn (it exists), then re-authenticate (az logout && az login, or restart the runtime / re-acquire the SPN token) and retry. Never add a broader role to “force” it — you’ll leave an over-grant behind.

The remaining entries (1, 2, 9–15) are read straight off the table: a missing or wrong-scope assignment (read the scope column against the target), a malformed scope id (az resource show --ids to confirm it’s real), the wrong role for the action (az provider operation show + the role’s Actions), an unmet ABAC condition, a cross-tenant guest gap, and the two non-RBAC lookalikes — a resource lock (az lock list; see Azure Resource Locks: Prevent Accidental Deletes and Changes in Production) and an Azure Policy deny rejecting the request body at admission (the Activity log names the policy, not an AuthorizationFailed; see Azure Policy Effects Decoded: Deny vs Audit vs Modify vs DeployIfNotExists).

Best practices

Security notes

Cost & sizing

RBAC itself is free — role assignments, definitions, deny-assignment reads, az role assignment list, Check access and az provider operation show cost nothing at any scale. There’s no per-assignment charge and no premium tier gating the model. So “cost” here is the operational and risk cost of getting RBAC wrong.

The expensive failure modes are over-granting and mis-diagnosis. Owner at a subscription to unblock a pipeline costs nothing on the invoice but creates a standing escalation path that an audit flags and a breach exploits — the cost arrives later as remediation. Mis-diagnosing AuthorizationFailed as “needs a bigger role” accretes privilege until no one can answer “who can do what here?” — a multi-week access review. And a CI principal blocked mid-release is revenue and engineer-hours: the ninety-minute Vantix incident was avoidable with one pre-flight check.

There are real service limits worth sizing against, because hitting them surfaces as its own failure (often RoleAssignmentLimitExceeded, not a plain AuthorizationFailed):

RBAC limit Approx. ceiling What hitting it looks like How to stay under
Role assignments per subscription ~4,000 (a few thousand) New assignments rejected; RoleAssignmentLimitExceeded Assign to groups, not individuals; clean up stale grants
Custom role definitions per tenant ~5,000 Can’t create more custom roles Reuse built-ins; consolidate near-duplicate custom roles
Assignment scope depth MG→sub→RG→resource Over-deep/odd scopes fail as “invalid” Assign at RG/sub for most cases
Deny assignments Platform-managed (not user-created) Hard blocks you can’t grant around Address the source (managed app/Blueprint)

The sizing rule is simple: assign to groups, scope to resource groups, reuse built-in roles, and prune. Per-user, per-resource, custom-role-for-everything sprawl marches a subscription toward the assignment ceiling and an unauditable mess. None of it costs rupees directly — it costs the thing more expensive than rupees: the ability to reason about your own access.

Interview & exam questions

1. An AuthorizationFailed names an object id, an action, and a scope. What does each tell you? The object id is the principal ARM evaluated (confirm you’re running as who you think — often an SPN/MI, not you); the action is the exact operation required (which role, which plane); the scope is where a covering assignment must exist. Read all three before running anything — they hand you the hypothesis.

2. A user has Contributor on a resource group but can’t assign a role to a managed identity. Why? Microsoft.Authorization/roleAssignments/write is in Contributor’s NotActions by design, so Contributors can’t escalate. Fix by granting Role Based Access Control Administrator or User Access Administrator at the scope — not Owner.

3. A principal has a granting role at a covering scope yet still gets AuthorizationFailed. Prime suspect? A deny assignment, which overrides any allow — from a managed application, Blueprint, or system protection, never the normal flow. Confirm via IAM → Deny assignments. You can’t grant around it; address the source.

4. Control-plane vs data-plane authorization, with an example? The control plane (ARM) authorises managing a resource via Actions; the data plane (the resource’s own endpoint) authorises operations on the data inside via DataActions. They evaluate separately: Storage Account Contributor manages the account but reads no blobs — you need Storage Blob Data Reader even as the account’s Owner.

5. A role assigned two minutes ago still fails. What’s happening and what do you do? Propagation lag and/or a stale token — live server-side but not yet in caches or the presented token (the error hints “if access was recently granted, refresh”). Wait and re-authenticate (az logout && az login); don’t add a broader role to force it.

6. How do you read a principal’s effective permissions? az role assignment list --assignee <objectId> --all --include-inherited, then check whether any row’s scope is the failing scope or an ancestor, with a role that supplies the action. Portal equivalent: IAM → Check access.

7. NotActions vs a deny assignment? NotActions are operations subtracted from one role’s Actions — another role can add them back. A deny assignment is absolute and overrides every allow at every scope. “This role doesn’t include X” vs “nothing may do X here.”

8. A storage account’s Owner can’t list its blobs. Why, and the fix? Reading blobs is data-plane (DataActions); control-plane Owner/Storage Account Contributor carry none. Grant a data role — Storage Blob Data Reader/Contributor — at the account or container scope.

9. What does “or the scope is invalid” actually mean? Two cases in one string: either the scope is valid and you lack a covering role (permissions — read effective access), or the id is malformed — wrong sub guid, missing segment, deleted resource (scope — fix the id). az resource show --ids tells them apart.

10. Access comes from a group, membership changed today, still fails. Why? Group membership is reflected only in a newly issued token; a cached/long-lived token carries old claims. Force a new token (az logout && az login / restart the runtime). The assignment may be perfect — the token is stale.

11. When choose Role Based Access Control Administrator over User Access Administrator? When a principal (often CI) must assign roles but should be constrained — RBAC Administrator supports a condition limiting which roles it can assign, the least-privilege choice. User Access Administrator can assign any role, broader than most automation needs.

12. A permissions-style failure on a delete, but the role is correct. Non-RBAC causes? A resource lock (CannotDelete/ReadOnly) overrides management ops regardless of role — az lock list. Or an Azure Policy deny rejected the request at admission (Activity log names the policy). Separate subsystems, separate fixes.

These map to AZ-104 (Administrator)manage Azure identities and governance, RBAC roles, scopes, and assignments — and AZ-500 (Security Engineer)manage identity and access, least privilege, Key Vault and managed-identity access, and the control/data-plane model. The IaC-grant angle touches AZ-400 / Terraform workflows. A compact cert-mapping for revision:

Question theme Primary cert Exam objective area
Parse the error; principal/action/scope AZ-104 Manage access to Azure resources (RBAC)
NotActions, Contributor-can’t-grant AZ-104 / AZ-500 Role definitions; least privilege
Deny assignments, managed apps AZ-500 Implement access control; guardrails
Control vs data plane (Storage/Key Vault) AZ-500 Secure data and applications; managed identities
Propagation/token, group membership AZ-104 Manage Entra identities and access
Locks vs Policy deny lookalikes AZ-104 Implement governance (locks, Policy)

Quick check

  1. The error names an object id you don’t recognise, the action Microsoft.Authorization/roleAssignments/write, and a resource-group scope. What two things does the action immediately tell you?
  2. A principal has Contributor at the subscription and still gets AuthorizationFailed writing a resource in a resource group under it. Name two distinct causes and how you’d tell them apart.
  3. True or false: adding Owner to a principal will fix a failure caused by a deny assignment.
  4. An app’s Owner can manage a storage account but gets AuthorizationFailed listing its blobs. What plane is the failing action on, and what role fixes it?
  5. A role assigned three minutes ago still isn’t working. What’s the most likely cause, and what should you not do?

Answers

  1. It tells you the call is a grant operation (so you need an access-management role — Owner, User Access Administrator, or RBAC Administrator — not Contributor, whose NotActions exclude Microsoft.Authorization/*/Write), and that the principal currently lacks that specific action at the named scope.
  2. Either a deny assignment overrides the allow (confirm via IAM → Deny assignments), or the action is one Contributor’s NotActions exclude — e.g. roleAssignments/write (confirm via az role definition list --name Contributor). Reading the action string distinguishes them: an authorization action points to NotActions; a normal resource action with a granting role points to a deny.
  3. False. A deny assignment overrides every allow, including Owner. The fix is to address the deny’s source (managed app / Blueprint / lock), not to grant a higher role.
  4. The failing action is on the data plane (DataActions) — control-plane roles like Owner grant no blob data access. Fix by assigning Storage Blob Data Reader (or Contributor) at the account or container scope.
  5. Propagation lag / a stale token — the assignment is live but not yet effective in caches or the presented token. Wait and re-authenticate (az logout && az login). Do not add a broader role to “force” it — you’ll leave an over-grant behind.

Glossary

Next steps

You can now read any AuthorizationFailed down to its principal, action and scope and fix it with the narrowest correct grant. Build outward:

AzureRBACAuthorizationFailedIAMManaged IdentityLeast PrivilegeTroubleshootingIdentity
Need this built for real?

Vinod is a Senior Cloud Architect (22+ yrs) — available for Azure / AWS / GCP architecture, landing zones, and migrations.

Work with me

Comments

Keep Reading