A user, pipeline or managed identity gets this:
The client 'deploy-sp' with object id '1111...' does not have authorization to perform
action 'Microsoft.Compute/virtualMachines/write' over scope '/subscriptions/.../resourceGroups/rg-prod/...'
or the scope is invalid. If access was recently granted, please refresh your credentials.
The error already tells you three things: who (object id), what (the action) and where (the scope). The fix is nearly always a role assignment that includes that action at that scope or a parent scope.
Decode the error
| Error code | Meaning | Usual fix |
|---|---|---|
AuthorizationFailed | Caller has no role with this action at this scope | Assign a role that includes the action |
LinkedAuthorizationFailed | Caller can act on the main resource but not on a linked one (subnet, Key Vault, image, identity) | Grant the linked action on the other resource, e.g. Microsoft.Network/virtualNetworks/subnets/join/action |
AuthorizationFailed right after access was granted | Token or role assignment not yet in effect | Wait, then get a new token (see below) |
ScopeLocked | A resource lock, not RBAC | Remove or change the lock if the change is approved |
RequestDisallowedByPolicy | Azure Policy denied it, not RBAC | Find the policy assignment; see the policy post |
403 on blob, queue, secret or key data | Data-plane permission missing; Owner and Contributor don't include data access | Assign a data role such as Storage Blob Data Reader or Key Vault Secrets User |
Who is the caller?
Get the object id of whatever is failing, so you look at the right principal.
# You
az ad signed-in-user show --query "{name:userPrincipalName, objectId:id}"
# A service principal or pipeline identity (by appId / client id)
az ad sp show --id <app-id> --query "{name:displayName, objectId:id}"
# A VM's system-assigned managed identity
az vm show -g rg-prod -n vm-app-01 --query identity.principalId -o tsv
# Check you are in the subscription you think you are
az account show --query "{sub:name, id:id, tenant:tenantId}"
Wrong subscription or tenant context is a frequent cause on its own. Pipelines that set the subscription in one step and run commands in another often lose it.
What roles does it have?
# All role assignments, including inherited ones and those via groups
az role assignment list --assignee <object-id> --all --include-inherited --include-groups \
--query "[].{role:roleDefinitionName, scope:scope}" -o table
Get-AzRoleAssignment -ObjectId <object-id> -ExpandPrincipalGroups |
Select-Object RoleDefinitionName, Scope, ObjectType
Then check whether any of those roles includes the action from the error:
az role definition list --name "Virtual Machine Contributor" \
--query "[0].permissions[0].{actions:actions, notActions:notActions, dataActions:dataActions}"
Wildcards count: Microsoft.Compute/* includes Microsoft.Compute/virtualMachines/write. A matching notActions entry removes it again.
To see what you can do at a scope, ask ARM directly. It returns your effective actions after combining all your roles:
az rest --method get \
--url "https://management.azure.com/subscriptions/<sub-id>/resourceGroups/rg-prod/providers/Microsoft.Authorization/permissions?api-version=2022-04-01"
Find the right role
List the operations a resource provider supports, to get exact action names:
az provider operation show --namespace Microsoft.Compute \
--query "resourceTypes[?name=='virtualMachines'].operations[].name" -o tsv
Find built-in roles that include an action:
az role definition list --query "[?permissions[0].actions[?contains(@, 'Microsoft.Compute/virtualMachines/start/action')]].roleName" -o tsv
This only finds roles that list the action explicitly; roles granting it through a wildcard won't appear. The portal's role picker and the Azure built-in roles documentation are the easier way to search.
Common needs and the smallest built-in role that covers them:
| Task | Role |
|---|---|
| Start, stop, restart VMs only | Virtual Machine Contributor (or a custom role with the start/deallocate actions) |
| Sign in to a VM with Entra ID | Virtual Machine User Login or Virtual Machine Administrator Login |
| Attach a NIC or service to a subnet | Network Contributor on the VNet, or a custom role with subnets/join/action |
| Read and write blobs | Storage Blob Data Contributor |
| Read Key Vault secrets (RBAC vaults) | Key Vault Secrets User |
| Assign roles to others | Role Based Access Control Administrator (can be constrained with conditions) or User Access Administrator |
| Read everything for audits | Reader |
Grant it
Assign at the narrowest scope that works, usually the resource group, not the subscription.
az role assignment create \
--assignee-object-id <object-id> \
--assignee-principal-type ServicePrincipal \
--role "Virtual Machine Contributor" \
--scope "/subscriptions/<sub-id>/resourceGroups/rg-prod"
Use --assignee-principal-type (User, Group or ServicePrincipal) with --assignee-object-id. It avoids a Microsoft Graph lookup and the replication errors you can get when assigning a role to an identity created seconds earlier.
Caution
Don't fix a permissions error by handing out
OwnerorContributorat subscription scope. It works, it hides the real requirement, and a leaked pipeline credential then controls the whole subscription.
It still fails after the role was assigned
| Check | Why |
|---|---|
| Wait a few minutes | Role assignments take time to replicate; check the current guidance for propagation time |
| Get a new token | Tokens issued before the change don't carry new group membership. Run az account clear then az login, or Connect-AzAccount again |
| Managed identity | Tokens for managed identities are cached by the platform and can take hours to reflect new group membership. Assign the role to the identity directly rather than through a group |
| PIM | An eligible assignment does nothing until activated. Activate the role, then sign in again |
| Deny assignment | Deny assignments (e.g. from managed applications or deployment stack deny settings) override role assignments |
| Conditions | A role assignment with an ABAC condition may not apply to this specific resource or request |
| Custom role scope | A custom role only applies within its assignableScopes |
Check for deny assignments at the scope:
Get-AzDenyAssignment -Scope "/subscriptions/<sub-id>/resourceGroups/rg-prod" |
Select-Object DenyAssignmentName, Description, Scope
Find recent authorization failures
Failed write operations are recorded in the Activity Log. With activity logs sent to Log Analytics:
AzureActivity
| where TimeGenerated > ago(24h)
| where ActivityStatusValue == "Failure"
| where ActivitySubstatusValue == "Forbidden" or Properties has "AuthorizationFailed"
| project TimeGenerated, Caller, OperationNameValue, ResourceGroup, _ResourceId
| order by TimeGenerated desc
Read-only (GET) operations aren't in the Activity Log, so a failing read won't show up here; use the error message itself.
Prevent it next time
- Define the roles each pipeline and workload identity needs in code (Bicep or Terraform) next to the resources, so new environments get them automatically.
- Assign roles to groups for people and directly to managed identities for workloads.
- Include linked-resource permissions (subnet join, Key Vault access, image read) in the deployment identity's role from the start.
- Review assignments at subscription scope every quarter and replace broad roles with resource group or custom roles.